chore: formatting and linting (#3261)

* chore: lint packages/payload

* chore: lint packages/db-postgres

* chore: lint packages/db-mongodb

* chore: update eslintrc exclusion rules

* chore: update eslintrc exclusion rules

* chore: lint misc files

* chore: run prettier through packages

* chore: run eslint on payload again

* chore: prettier misc files

* chore: prettier docs
This commit is contained in:
Alessio Gravili
2023-09-01 17:39:44 +02:00
committed by GitHub
parent 5f7673d735
commit ae7d6f97d2
1403 changed files with 48072 additions and 46231 deletions

View File

@@ -3,7 +3,7 @@ module.exports = {
overrides: [ overrides: [
{ {
extends: ['plugin:@typescript-eslint/disable-type-checked'], extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs'], files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
}, },
{ {
files: ['packages/eslint-config-payload/**'], files: ['packages/eslint-config-payload/**'],

View File

@@ -1,6 +1,6 @@
name: Bug Report name: Bug Report
description: Create a bug report for Payload description: Create a bug report for Payload
labels: ["possible-bug"] labels: ['possible-bug']
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -9,6 +9,7 @@
**NOTE:** The goal is to isolate the problem by reducing the number of `collections/globals/fields` you add to the `test/_community` folder. This folder is _not_ meant for you to copy your project into, but rather recreate the issue you are experiencing with minimal config. **NOTE:** The goal is to isolate the problem by reducing the number of `collections/globals/fields` you add to the `test/_community` folder. This folder is _not_ meant for you to copy your project into, but rather recreate the issue you are experiencing with minimal config.
## Example test directory file tree ## Example test directory file tree
```text ```text
. .
├── config.ts ├── config.ts
@@ -27,9 +28,11 @@ The directory split up in this way specifically to reduce friction when creating
<br /> <br />
## Testing is optional but encouraged ## Testing is optional but encouraged
An issue does not need to have failing tests — reproduction steps with your forked repo are enough at this point. Some people like to dive deeper and we want to give you the guidance/tools to do so. Read more below: An issue does not need to have failing tests — reproduction steps with your forked repo are enough at this point. Some people like to dive deeper and we want to give you the guidance/tools to do so. Read more below:
### Running integration tests (Payload API tests) ### Running integration tests (Payload API tests)
First install [Jest Runner for VSVode](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner). First install [Jest Runner for VSVode](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner).
There are a couple ways run integration tests: There are a couple ways run integration tests:
@@ -45,7 +48,9 @@ There are a couple ways run integration tests:
``` ```
### Running E2E tests (Admin Panel UI tests) ### Running E2E tests (Admin Panel UI tests)
The easiest way to run E2E tests is to install The easiest way to run E2E tests is to install
- [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) - [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
- [Playwright Runner](https://marketplace.visualstudio.com/items?itemName=ortoni.ortoni) - [Playwright Runner](https://marketplace.visualstudio.com/items?itemName=ortoni.ortoni)
@@ -53,6 +58,6 @@ Once they are installed you can open the `testing` tab in vscode sidebar and dri
<img src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/github/e2e-debug.png" /> <img src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/github/e2e-debug.png" />
#### Notes #### Notes
- It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart. The default credentials are `dev@payloadcms.com` as email and `test` as password. - It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart. The default credentials are `dev@payloadcms.com` as email and `test` as password.

File diff suppressed because it is too large Load Diff

94
.vscode/launch.json vendored
View File

@@ -1,112 +1,112 @@
{ {
// Use IntelliSense to learn about possible attributes. // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes. // Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [ "configurations": [
{ {
"command": "pnpm run dev _community", "command": "pnpm run dev _community",
"cwd": "${workspaceFolder}",
"name": "Run Dev Community", "name": "Run Dev Community",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node-terminal"
"cwd": "${workspaceFolder}"
}, },
{ {
"command": "pnpm run dev fields", "command": "pnpm run dev fields",
"cwd": "${workspaceFolder}",
"name": "Run Dev Fields", "name": "Run Dev Fields",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node-terminal"
"cwd": "${workspaceFolder}"
}, },
{ {
"command": "pnpm run dev:postgres postgres -- -I", // Allow input "command": "pnpm run dev:postgres postgres -- -I", // Allow input
"cwd": "${workspaceFolder}",
"name": "Run Dev Postgres", "name": "Run Dev Postgres",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node-terminal"
"cwd": "${workspaceFolder}"
}, },
{ {
"command": "pnpm run dev versions", "command": "pnpm run dev versions",
"cwd": "${workspaceFolder}",
"name": "Run Dev Versions", "name": "Run Dev Versions",
"request": "launch", "request": "launch",
"type": "node-terminal", "type": "node-terminal"
"cwd": "${workspaceFolder}"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate", "command": "ts-node src/bin/migrate.ts migrate",
"request": "launch", "env": {
"PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true",
},
"name": "Migrate CLI - migrate", "name": "Migrate CLI - migrate",
"env": {
"PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_DATABASE": "postgres",
// "PAYLOAD_DROP_DATABASE": "true",
},
"outputCapture": "std", "outputCapture": "std",
"request": "launch",
"type": "node-terminal"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate:status", "command": "ts-node src/bin/migrate.ts migrate:status",
"request": "launch",
"name": "Migrate CLI - status",
"env": { "env": {
"PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts", "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_DATABASE": "postgres", "PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true", // "PAYLOAD_DROP_DATABASE": "true",
}, },
"name": "Migrate CLI - status",
"outputCapture": "std", "outputCapture": "std",
"request": "launch",
"type": "node-terminal"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate:create yass", "command": "ts-node src/bin/migrate.ts migrate:create yass",
"request": "launch", "env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true",
},
"name": "Migrate CLI - create", "name": "Migrate CLI - create",
"env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres",
// "PAYLOAD_DROP_DATABASE": "true",
},
"outputCapture": "std", "outputCapture": "std",
"request": "launch",
"type": "node-terminal"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate:down", "command": "ts-node src/bin/migrate.ts migrate:down",
"request": "launch", "env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true",
},
"name": "Migrate CLI - down", "name": "Migrate CLI - down",
"env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres",
// "PAYLOAD_DROP_DATABASE": "true",
},
"outputCapture": "std", "outputCapture": "std",
"request": "launch",
"type": "node-terminal"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate:reset", "command": "ts-node src/bin/migrate.ts migrate:reset",
"request": "launch",
"name": "Migrate CLI - reset",
"env": { "env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts", // "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts", "PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres", "PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true", // "PAYLOAD_DROP_DATABASE": "true",
}, },
"name": "Migrate CLI - reset",
"outputCapture": "std", "outputCapture": "std",
"request": "launch",
"type": "node-terminal"
}, },
{ {
"type": "node-terminal",
"command": "ts-node src/bin/migrate.ts migrate:refresh", "command": "ts-node src/bin/migrate.ts migrate:refresh",
"request": "launch",
"name": "Migrate CLI - refresh",
"env": { "env": {
// "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts", // "PAYLOAD_CONFIG_PATH": "test/migrations-cli/config.ts",
"PAYLOAD_CONFIG_PATH": "test/postgres/config.ts", "PAYLOAD_CONFIG_PATH": "test/postgres/config.ts",
"PAYLOAD_DATABASE": "postgres", "PAYLOAD_DATABASE": "postgres"
// "PAYLOAD_DROP_DATABASE": "true", // "PAYLOAD_DROP_DATABASE": "true",
}, },
"name": "Migrate CLI - refresh",
"outputCapture": "std", "outputCapture": "std",
}, "request": "launch",
] "type": "node-terminal"
}
],
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0"
} }

View File

@@ -1,457 +1,402 @@
# [1.14.0](https://github.com/payloadcms/payload/compare/v1.13.4...v1.14.0) (2023-08-16) # [1.14.0](https://github.com/payloadcms/payload/compare/v1.13.4...v1.14.0) (2023-08-16)
### Bug Fixes ### Bug Fixes
* DatePicker showing only selected day by default ([#3169](https://github.com/payloadcms/payload/issues/3169)) ([edcb393](https://github.com/payloadcms/payload/commit/edcb3933cfb4532180c822135ea6a8be928e0fdc)) - DatePicker showing only selected day by default ([#3169](https://github.com/payloadcms/payload/issues/3169)) ([edcb393](https://github.com/payloadcms/payload/commit/edcb3933cfb4532180c822135ea6a8be928e0fdc))
* only allow redirects to /admin sub-routes ([c0f05a1](https://github.com/payloadcms/payload/commit/c0f05a1c38fb9c958de920fabb698b5ecfb661f0)) - only allow redirects to /admin sub-routes ([c0f05a1](https://github.com/payloadcms/payload/commit/c0f05a1c38fb9c958de920fabb698b5ecfb661f0))
* passes in height to resizeOptions upload option to allow height resize ([#3171](https://github.com/payloadcms/payload/issues/3171)) ([7963d04](https://github.com/payloadcms/payload/commit/7963d04a27888eb5a12d0ab37f2082cd33638abd)) - passes in height to resizeOptions upload option to allow height resize ([#3171](https://github.com/payloadcms/payload/issues/3171)) ([7963d04](https://github.com/payloadcms/payload/commit/7963d04a27888eb5a12d0ab37f2082cd33638abd))
* WhereBuilder component does not accept all valid Where queries ([#3087](https://github.com/payloadcms/payload/issues/3087)) ([fdfdfc8](https://github.com/payloadcms/payload/commit/fdfdfc83f36a958971f8e4e4f9f5e51560cb26e0)) - WhereBuilder component does not accept all valid Where queries ([#3087](https://github.com/payloadcms/payload/issues/3087)) ([fdfdfc8](https://github.com/payloadcms/payload/commit/fdfdfc83f36a958971f8e4e4f9f5e51560cb26e0))
### Features ### Features
* add afterOperation hook ([#2697](https://github.com/payloadcms/payload/issues/2697)) ([33686c6](https://github.com/payloadcms/payload/commit/33686c6db8373a16d7f6b0192e0701bf15881aa4)) - add afterOperation hook ([#2697](https://github.com/payloadcms/payload/issues/2697)) ([33686c6](https://github.com/payloadcms/payload/commit/33686c6db8373a16d7f6b0192e0701bf15881aa4))
* add support for hotkeys ([#1821](https://github.com/payloadcms/payload/issues/1821)) ([942cfec](https://github.com/payloadcms/payload/commit/942cfec286ff050e13417b037cca64b9d757d868)) - add support for hotkeys ([#1821](https://github.com/payloadcms/payload/issues/1821)) ([942cfec](https://github.com/payloadcms/payload/commit/942cfec286ff050e13417b037cca64b9d757d868))
* Added Azerbaijani language file ([#3164](https://github.com/payloadcms/payload/issues/3164)) ([63e3063](https://github.com/payloadcms/payload/commit/63e3063b9ecc1afd62d7a287a798d41215008f2a)) - Added Azerbaijani language file ([#3164](https://github.com/payloadcms/payload/issues/3164)) ([63e3063](https://github.com/payloadcms/payload/commit/63e3063b9ecc1afd62d7a287a798d41215008f2a))
* allow async relationship filter options ([#2951](https://github.com/payloadcms/payload/issues/2951)) ([bad3638](https://github.com/payloadcms/payload/commit/bad363882c9d00d3c73547ca3329eba988e728ff)) - allow async relationship filter options ([#2951](https://github.com/payloadcms/payload/issues/2951)) ([bad3638](https://github.com/payloadcms/payload/commit/bad363882c9d00d3c73547ca3329eba988e728ff))
* Improve admin dashboard accessibility ([#3053](https://github.com/payloadcms/payload/issues/3053)) ([e03a8e6](https://github.com/payloadcms/payload/commit/e03a8e6b030e82a17e1cdae5b4032433cf9c75a4)) - Improve admin dashboard accessibility ([#3053](https://github.com/payloadcms/payload/issues/3053)) ([e03a8e6](https://github.com/payloadcms/payload/commit/e03a8e6b030e82a17e1cdae5b4032433cf9c75a4))
* improve field ops ([#3172](https://github.com/payloadcms/payload/issues/3172)) ([d91b44c](https://github.com/payloadcms/payload/commit/d91b44cbb3fd526caca2a6f4bd30fd06ede3a5da)) - improve field ops ([#3172](https://github.com/payloadcms/payload/issues/3172)) ([d91b44c](https://github.com/payloadcms/payload/commit/d91b44cbb3fd526caca2a6f4bd30fd06ede3a5da))
* make PAYLOAD_CONFIG_PATH optional ([#2839](https://github.com/payloadcms/payload/issues/2839)) ([5744de7](https://github.com/payloadcms/payload/commit/5744de7ec63e3f17df7e02a7cc827818a79dbbb8)) - make PAYLOAD_CONFIG_PATH optional ([#2839](https://github.com/payloadcms/payload/issues/2839)) ([5744de7](https://github.com/payloadcms/payload/commit/5744de7ec63e3f17df7e02a7cc827818a79dbbb8))
* text alignment for richtext editor ([#2803](https://github.com/payloadcms/payload/issues/2803)) ([a0b13a5](https://github.com/payloadcms/payload/commit/a0b13a5b01fa0d7f4c4dffd1895bfe507e5c676d)) - text alignment for richtext editor ([#2803](https://github.com/payloadcms/payload/issues/2803)) ([a0b13a5](https://github.com/payloadcms/payload/commit/a0b13a5b01fa0d7f4c4dffd1895bfe507e5c676d))
## [1.13.4](https://github.com/payloadcms/payload/compare/v1.13.3...v1.13.4) (2023-08-11) ## [1.13.4](https://github.com/payloadcms/payload/compare/v1.13.3...v1.13.4) (2023-08-11)
### Bug Fixes ### Bug Fixes
* correctly passes block path inside buildFieldSchemaMap ([#3162](https://github.com/payloadcms/payload/issues/3162)) ([3c60abd](https://github.com/payloadcms/payload/commit/3c60abd61aaf24d49712c80bcbd0f1113c22b85a)) - correctly passes block path inside buildFieldSchemaMap ([#3162](https://github.com/payloadcms/payload/issues/3162)) ([3c60abd](https://github.com/payloadcms/payload/commit/3c60abd61aaf24d49712c80bcbd0f1113c22b85a))
## [1.13.3](https://github.com/payloadcms/payload/compare/v1.13.2...v1.13.3) (2023-08-11) ## [1.13.3](https://github.com/payloadcms/payload/compare/v1.13.2...v1.13.3) (2023-08-11)
### Bug Fixes ### Bug Fixes
* unable to add arrays inside secondary named tabs ([#3158](https://github.com/payloadcms/payload/issues/3158)) ([cb04d4a](https://github.com/payloadcms/payload/commit/cb04d4a82a68a764330582b93882d422b32c2527)) - unable to add arrays inside secondary named tabs ([#3158](https://github.com/payloadcms/payload/issues/3158)) ([cb04d4a](https://github.com/payloadcms/payload/commit/cb04d4a82a68a764330582b93882d422b32c2527))
## [1.13.2](https://github.com/payloadcms/payload/compare/v1.13.1...v1.13.2) (2023-08-10) ## [1.13.2](https://github.com/payloadcms/payload/compare/v1.13.1...v1.13.2) (2023-08-10)
## [1.13.1](https://github.com/payloadcms/payload/compare/v1.13.0...v1.13.1) (2023-08-08) ## [1.13.1](https://github.com/payloadcms/payload/compare/v1.13.0...v1.13.1) (2023-08-08)
### Bug Fixes ### Bug Fixes
* updates addFieldRow and replaceFieldRow rowIndex insertion ([#3145](https://github.com/payloadcms/payload/issues/3145)) ([f5cf546](https://github.com/payloadcms/payload/commit/f5cf546e1918de66998d5f0e5410bfbc1f054567)) - updates addFieldRow and replaceFieldRow rowIndex insertion ([#3145](https://github.com/payloadcms/payload/issues/3145)) ([f5cf546](https://github.com/payloadcms/payload/commit/f5cf546e1918de66998d5f0e5410bfbc1f054567))
# [1.13.0](https://github.com/payloadcms/payload/compare/v1.12.0...v1.13.0) (2023-08-08) # [1.13.0](https://github.com/payloadcms/payload/compare/v1.12.0...v1.13.0) (2023-08-08)
### Bug Fixes ### Bug Fixes
* `setPreference()` return type ([#3125](https://github.com/payloadcms/payload/issues/3125)) ([463d6bb](https://github.com/payloadcms/payload/commit/463d6bbec66e61523bae3869df88bd98e7617390)) - `setPreference()` return type ([#3125](https://github.com/payloadcms/payload/issues/3125)) ([463d6bb](https://github.com/payloadcms/payload/commit/463d6bbec66e61523bae3869df88bd98e7617390))
* absolute staticURL admin thumbnails ([#3135](https://github.com/payloadcms/payload/issues/3135)) ([1039f39](https://github.com/payloadcms/payload/commit/1039f39c09260537616b22228080466e8df6e981)) - absolute staticURL admin thumbnails ([#3135](https://github.com/payloadcms/payload/issues/3135)) ([1039f39](https://github.com/payloadcms/payload/commit/1039f39c09260537616b22228080466e8df6e981))
* adding and replacing similarly shaped block configs ([#3140](https://github.com/payloadcms/payload/issues/3140)) ([8e188cf](https://github.com/payloadcms/payload/commit/8e188cfe61db808c94d726967affdadf2e5abb9f)) - adding and replacing similarly shaped block configs ([#3140](https://github.com/payloadcms/payload/issues/3140)) ([8e188cf](https://github.com/payloadcms/payload/commit/8e188cfe61db808c94d726967affdadf2e5abb9f))
### Features ### Features
* default tab labels from name ([#3129](https://github.com/payloadcms/payload/issues/3129)) ([e8f0516](https://github.com/payloadcms/payload/commit/e8f05165eb3a28c00deb11931db01ad1f8c75c74)) - default tab labels from name ([#3129](https://github.com/payloadcms/payload/issues/3129)) ([e8f0516](https://github.com/payloadcms/payload/commit/e8f05165eb3a28c00deb11931db01ad1f8c75c74))
* radio and select fields are filterable by options ([#3136](https://github.com/payloadcms/payload/issues/3136)) ([b117e73](https://github.com/payloadcms/payload/commit/b117e7346434bfc8edbfa92f5db45f63c57bab08)) - radio and select fields are filterable by options ([#3136](https://github.com/payloadcms/payload/issues/3136)) ([b117e73](https://github.com/payloadcms/payload/commit/b117e7346434bfc8edbfa92f5db45f63c57bab08))
* recursive saveToJWT field support ([#3130](https://github.com/payloadcms/payload/issues/3130)) ([c6e0908](https://github.com/payloadcms/payload/commit/c6e09080767dad2ab8128ba330b2b344bb25ac6f)) - recursive saveToJWT field support ([#3130](https://github.com/payloadcms/payload/issues/3130)) ([c6e0908](https://github.com/payloadcms/payload/commit/c6e09080767dad2ab8128ba330b2b344bb25ac6f))
# [1.12.0](https://github.com/payloadcms/payload/compare/v1.11.8...v1.12.0) (2023-08-04) # [1.12.0](https://github.com/payloadcms/payload/compare/v1.11.8...v1.12.0) (2023-08-04)
### Bug Fixes ### Bug Fixes
* excludes useAsTitle field from searchableFields in collection view ([#3105](https://github.com/payloadcms/payload/issues/3105)) ([8c4d251](https://github.com/payloadcms/payload/commit/8c4d2514b0f195e0059c6063346199785979c70c)) - excludes useAsTitle field from searchableFields in collection view ([#3105](https://github.com/payloadcms/payload/issues/3105)) ([8c4d251](https://github.com/payloadcms/payload/commit/8c4d2514b0f195e0059c6063346199785979c70c))
* relationship field filter long titles ([#3113](https://github.com/payloadcms/payload/issues/3113)) ([da27a8a](https://github.com/payloadcms/payload/commit/da27a8aadbb103c5f6fe0ccc62c032876851b88f)) - relationship field filter long titles ([#3113](https://github.com/payloadcms/payload/issues/3113)) ([da27a8a](https://github.com/payloadcms/payload/commit/da27a8aadbb103c5f6fe0ccc62c032876851b88f))
* wrong links in verification and forgot password emails if serverURL not set ([#3010](https://github.com/payloadcms/payload/issues/3010)) ([6a189c6](https://github.com/payloadcms/payload/commit/6a189c6548b233aba64598af8804a56ec47e45f0)) - wrong links in verification and forgot password emails if serverURL not set ([#3010](https://github.com/payloadcms/payload/issues/3010)) ([6a189c6](https://github.com/payloadcms/payload/commit/6a189c6548b233aba64598af8804a56ec47e45f0))
### Features ### Features
* add support for sharp resize options ([#2844](https://github.com/payloadcms/payload/issues/2844)) ([144bb81](https://github.com/payloadcms/payload/commit/144bb81721814c19eb4957d4c8fcc845c73e2aa4)) - add support for sharp resize options ([#2844](https://github.com/payloadcms/payload/issues/2844)) ([144bb81](https://github.com/payloadcms/payload/commit/144bb81721814c19eb4957d4c8fcc845c73e2aa4))
* allows for upload relationship drawer to be opened ([#3108](https://github.com/payloadcms/payload/issues/3108)) ([ea73e68](https://github.com/payloadcms/payload/commit/ea73e689ac46f2a7ba3b6c34e7a190944b5d5868)) - allows for upload relationship drawer to be opened ([#3108](https://github.com/payloadcms/payload/issues/3108)) ([ea73e68](https://github.com/payloadcms/payload/commit/ea73e689ac46f2a7ba3b6c34e7a190944b5d5868))
* option to pre-fill login credentials automatically ([#3021](https://github.com/payloadcms/payload/issues/3021)) ([c5756ed](https://github.com/payloadcms/payload/commit/c5756ed4a13b46bc73ae7b23309d6e9980fc81bf)) - option to pre-fill login credentials automatically ([#3021](https://github.com/payloadcms/payload/issues/3021)) ([c5756ed](https://github.com/payloadcms/payload/commit/c5756ed4a13b46bc73ae7b23309d6e9980fc81bf))
* programmatic control over array and block rows inside the form ([#3110](https://github.com/payloadcms/payload/issues/3110)) ([a78c463](https://github.com/payloadcms/payload/commit/a78c4631b4aabb5b57448ab21ef98749b1cf1935)) - programmatic control over array and block rows inside the form ([#3110](https://github.com/payloadcms/payload/issues/3110)) ([a78c463](https://github.com/payloadcms/payload/commit/a78c4631b4aabb5b57448ab21ef98749b1cf1935))
* set JWT token field name with saveToJWT ([#3126](https://github.com/payloadcms/payload/issues/3126)) ([356f174](https://github.com/payloadcms/payload/commit/356f174b9ff601facb0062d0b65db18803ef2aa2)) - set JWT token field name with saveToJWT ([#3126](https://github.com/payloadcms/payload/issues/3126)) ([356f174](https://github.com/payloadcms/payload/commit/356f174b9ff601facb0062d0b65db18803ef2aa2))
## [1.11.8](https://github.com/payloadcms/payload/compare/v1.11.7...v1.11.8) (2023-07-31) ## [1.11.8](https://github.com/payloadcms/payload/compare/v1.11.7...v1.11.8) (2023-07-31)
## [1.11.7](https://github.com/payloadcms/payload/compare/v1.11.6...v1.11.7) (2023-07-27) ## [1.11.7](https://github.com/payloadcms/payload/compare/v1.11.6...v1.11.7) (2023-07-27)
### Bug Fixes ### Bug Fixes
* [#3062](https://github.com/payloadcms/payload/issues/3062) ([0280953](https://github.com/payloadcms/payload/commit/02809532b484d9018c6528cfbbbb43abfd55a540)) - [#3062](https://github.com/payloadcms/payload/issues/3062) ([0280953](https://github.com/payloadcms/payload/commit/02809532b484d9018c6528cfbbbb43abfd55a540))
* array row deletion ([#3062](https://github.com/payloadcms/payload/issues/3062)) ([cf9795b](https://github.com/payloadcms/payload/commit/cf9795b8d8b53c48335ff4c32c6c51b3de4f7bc9)) - array row deletion ([#3062](https://github.com/payloadcms/payload/issues/3062)) ([cf9795b](https://github.com/payloadcms/payload/commit/cf9795b8d8b53c48335ff4c32c6c51b3de4f7bc9))
* incorrect image rotation after being processed by sharp ([#3081](https://github.com/payloadcms/payload/issues/3081)) ([0a91950](https://github.com/payloadcms/payload/commit/0a91950f052ce40427801e6561a0f676354a2ca4)) - incorrect image rotation after being processed by sharp ([#3081](https://github.com/payloadcms/payload/issues/3081)) ([0a91950](https://github.com/payloadcms/payload/commit/0a91950f052ce40427801e6561a0f676354a2ca4))
### Features ### Features
* ability to add context to payload's request object ([#2796](https://github.com/payloadcms/payload/issues/2796)) ([67ba131](https://github.com/payloadcms/payload/commit/67ba131cc61f3d3b30ef9ef7fc150344ca82da2f)) - ability to add context to payload's request object ([#2796](https://github.com/payloadcms/payload/issues/2796)) ([67ba131](https://github.com/payloadcms/payload/commit/67ba131cc61f3d3b30ef9ef7fc150344ca82da2f))
## [1.11.6](https://github.com/payloadcms/payload/compare/v1.11.5...v1.11.6) (2023-07-25) ## [1.11.6](https://github.com/payloadcms/payload/compare/v1.11.5...v1.11.6) (2023-07-25)
### Bug Fixes ### Bug Fixes
* **collections:admin:** Enable adminThumbnail fn execution on all types ([2c74e93](https://github.com/payloadcms/payload/commit/2c74e9396a216a033e2bacdf189b7f28a0f97505)) - **collections:admin:** Enable adminThumbnail fn execution on all types ([2c74e93](https://github.com/payloadcms/payload/commit/2c74e9396a216a033e2bacdf189b7f28a0f97505))
* threads hasMaxRows into ArrayAction components within blocks and arrays ([#3066](https://github.com/payloadcms/payload/issues/3066)) ([d43c83d](https://github.com/payloadcms/payload/commit/d43c83dad1bab5b05f4fcbae7d41de369905797c)) - threads hasMaxRows into ArrayAction components within blocks and arrays ([#3066](https://github.com/payloadcms/payload/issues/3066)) ([d43c83d](https://github.com/payloadcms/payload/commit/d43c83dad1bab5b05f4fcbae7d41de369905797c))
## [1.11.5](https://github.com/payloadcms/payload/compare/v1.11.4...v1.11.5) (2023-07-25) ## [1.11.5](https://github.com/payloadcms/payload/compare/v1.11.4...v1.11.5) (2023-07-25)
### Bug Fixes ### Bug Fixes
* admin route not mounting on production serve ([#3071](https://github.com/payloadcms/payload/issues/3071)) ([e718668](https://github.com/payloadcms/payload/commit/e71866856fffefcfb61dd3d29135cccb66939a62)) - admin route not mounting on production serve ([#3071](https://github.com/payloadcms/payload/issues/3071)) ([e718668](https://github.com/payloadcms/payload/commit/e71866856fffefcfb61dd3d29135cccb66939a62))
## [1.11.4](https://github.com/payloadcms/payload/compare/v1.11.3...v1.11.4) (2023-07-25) ## [1.11.4](https://github.com/payloadcms/payload/compare/v1.11.3...v1.11.4) (2023-07-25)
### Bug Fixes ### Bug Fixes
* if arrayFieldType rows are undefined, page would crash ([#3049](https://github.com/payloadcms/payload/issues/3049)) ([08377cc](https://github.com/payloadcms/payload/commit/08377cc5a7ea9d02350177e2e1d69390ee97af78)) - if arrayFieldType rows are undefined, page would crash ([#3049](https://github.com/payloadcms/payload/issues/3049)) ([08377cc](https://github.com/payloadcms/payload/commit/08377cc5a7ea9d02350177e2e1d69390ee97af78))
### Features ### Features
* bump mongoose and mongoose-paginate versions ([#3025](https://github.com/payloadcms/payload/issues/3025)) ([41d3eee](https://github.com/payloadcms/payload/commit/41d3eee35f3855798a5c3372f8ad7c742a7810f7)) - bump mongoose and mongoose-paginate versions ([#3025](https://github.com/payloadcms/payload/issues/3025)) ([41d3eee](https://github.com/payloadcms/payload/commit/41d3eee35f3855798a5c3372f8ad7c742a7810f7))
* improve keyboard focus styles ([#3011](https://github.com/payloadcms/payload/issues/3011)) ([080e619](https://github.com/payloadcms/payload/commit/080e6195ef39ec858fbb115e8f554a8dfc436438)) - improve keyboard focus styles ([#3011](https://github.com/payloadcms/payload/issues/3011)) ([080e619](https://github.com/payloadcms/payload/commit/080e6195ef39ec858fbb115e8f554a8dfc436438))
* solidifies bundler adapter pattern ([#3044](https://github.com/payloadcms/payload/issues/3044)) ([641c765](https://github.com/payloadcms/payload/commit/641c765fb921e162c98f09218929348037dd0f88)) - solidifies bundler adapter pattern ([#3044](https://github.com/payloadcms/payload/issues/3044)) ([641c765](https://github.com/payloadcms/payload/commit/641c765fb921e162c98f09218929348037dd0f88))
## [1.11.3](https://github.com/payloadcms/payload/compare/v1.11.2...v1.11.3) (2023-07-19) ## [1.11.3](https://github.com/payloadcms/payload/compare/v1.11.2...v1.11.3) (2023-07-19)
### Bug Fixes ### Bug Fixes
* adds backdrop blur to button ([#3006](https://github.com/payloadcms/payload/issues/3006)) ([4233426](https://github.com/payloadcms/payload/commit/42334263bbc6219be92c5728f1a4ac6c8d2d1306)) - adds backdrop blur to button ([#3006](https://github.com/payloadcms/payload/issues/3006)) ([4233426](https://github.com/payloadcms/payload/commit/42334263bbc6219be92c5728f1a4ac6c8d2d1306))
* rich text link element not validating on create ([#3014](https://github.com/payloadcms/payload/issues/3014)) ([60fca40](https://github.com/payloadcms/payload/commit/60fca40780d4ddd8e684a455de55c566ec91e223)) - rich text link element not validating on create ([#3014](https://github.com/payloadcms/payload/issues/3014)) ([60fca40](https://github.com/payloadcms/payload/commit/60fca40780d4ddd8e684a455de55c566ec91e223))
### Features ### Features
* auto-login in config capability ([#3009](https://github.com/payloadcms/payload/issues/3009)) ([733fc0b](https://github.com/payloadcms/payload/commit/733fc0b2d0cf0f2d58c8a28e84776f883774b0e0)) - auto-login in config capability ([#3009](https://github.com/payloadcms/payload/issues/3009)) ([733fc0b](https://github.com/payloadcms/payload/commit/733fc0b2d0cf0f2d58c8a28e84776f883774b0e0))
* returns queried user alongside refreshed token ([#2813](https://github.com/payloadcms/payload/issues/2813)) ([2fc03f1](https://github.com/payloadcms/payload/commit/2fc03f196e4e5fa0ad3369ec976c0b6889ebda88)) - returns queried user alongside refreshed token ([#2813](https://github.com/payloadcms/payload/issues/2813)) ([2fc03f1](https://github.com/payloadcms/payload/commit/2fc03f196e4e5fa0ad3369ec976c0b6889ebda88))
* support logger destination ([#2896](https://github.com/payloadcms/payload/issues/2896)) ([cd0bf68](https://github.com/payloadcms/payload/commit/cd0bf68a6150b1adbdb9ee318ac0a06c4476aa4d)) - support logger destination ([#2896](https://github.com/payloadcms/payload/issues/2896)) ([cd0bf68](https://github.com/payloadcms/payload/commit/cd0bf68a6150b1adbdb9ee318ac0a06c4476aa4d))
## [1.11.2](https://github.com/payloadcms/payload/compare/v1.11.1...v1.11.2) (2023-07-14) ## [1.11.2](https://github.com/payloadcms/payload/compare/v1.11.1...v1.11.2) (2023-07-14)
### Features ### Features
* adds array, collapsible, tab and group error states ([4925f90](https://github.com/payloadcms/payload/commit/4925f90b5f5c8fb8092bf4e8d88d5e0c1846b094)) - adds array, collapsible, tab and group error states ([4925f90](https://github.com/payloadcms/payload/commit/4925f90b5f5c8fb8092bf4e8d88d5e0c1846b094))
## [1.11.1](https://github.com/payloadcms/payload/compare/v1.11.0...v1.11.1) (2023-07-11) ## [1.11.1](https://github.com/payloadcms/payload/compare/v1.11.0...v1.11.1) (2023-07-11)
### Bug Fixes ### Bug Fixes
* [#2980](https://github.com/payloadcms/payload/issues/2980), locale=all was not iterating through arrays / blocks ([d6bfba7](https://github.com/payloadcms/payload/commit/d6bfba72a6b1a84bc5bb9dd14c7ce31d7afcbc1c)) - [#2980](https://github.com/payloadcms/payload/issues/2980), locale=all was not iterating through arrays / blocks ([d6bfba7](https://github.com/payloadcms/payload/commit/d6bfba72a6b1a84bc5bb9dd14c7ce31d7afcbc1c))
* anchor Button component respect margins ([#2648](https://github.com/payloadcms/payload/issues/2648)) ([1877d22](https://github.com/payloadcms/payload/commit/1877d2247c89ca5c8e1f0e1f989154d54768fed8)) - anchor Button component respect margins ([#2648](https://github.com/payloadcms/payload/issues/2648)) ([1877d22](https://github.com/payloadcms/payload/commit/1877d2247c89ca5c8e1f0e1f989154d54768fed8))
# [1.11.0](https://github.com/payloadcms/payload/compare/v1.10.5...v1.11.0) (2023-07-05) # [1.11.0](https://github.com/payloadcms/payload/compare/v1.10.5...v1.11.0) (2023-07-05)
### Bug Fixes ### Bug Fixes
* ensures fields within blocks respect field level access control ([#2969](https://github.com/payloadcms/payload/issues/2969)) ([5b79067](https://github.com/payloadcms/payload/commit/5b79067cc14874abbd1e1a5b6e619d41571b187f)) - ensures fields within blocks respect field level access control ([#2969](https://github.com/payloadcms/payload/issues/2969)) ([5b79067](https://github.com/payloadcms/payload/commit/5b79067cc14874abbd1e1a5b6e619d41571b187f))
* ensures rows always have id's ([#2968](https://github.com/payloadcms/payload/issues/2968)) ([04851d0](https://github.com/payloadcms/payload/commit/04851d0dc99e4a3df0a1ac642e9a4b9a3c06d8a1)) - ensures rows always have id's ([#2968](https://github.com/payloadcms/payload/issues/2968)) ([04851d0](https://github.com/payloadcms/payload/commit/04851d0dc99e4a3df0a1ac642e9a4b9a3c06d8a1))
* GraphQL type for number field ([#2954](https://github.com/payloadcms/payload/issues/2954)) ([29d8bf0](https://github.com/payloadcms/payload/commit/29d8bf0927038d2305218e5a6b811e0c4039d617)) - GraphQL type for number field ([#2954](https://github.com/payloadcms/payload/issues/2954)) ([29d8bf0](https://github.com/payloadcms/payload/commit/29d8bf0927038d2305218e5a6b811e0c4039d617))
* nested richtext bug and test ([#2966](https://github.com/payloadcms/payload/issues/2966)) ([801f609](https://github.com/payloadcms/payload/commit/801f60939b1bb4e33fbabe1f9a3c4a04a47912db)) - nested richtext bug and test ([#2966](https://github.com/payloadcms/payload/issues/2966)) ([801f609](https://github.com/payloadcms/payload/commit/801f60939b1bb4e33fbabe1f9a3c4a04a47912db))
* properly threads custom react-select props through relationship field ([#2973](https://github.com/payloadcms/payload/issues/2973)) ([79393e8](https://github.com/payloadcms/payload/commit/79393e8cf0b79b31fa711536e0bc22b1a251468a)) - properly threads custom react-select props through relationship field ([#2973](https://github.com/payloadcms/payload/issues/2973)) ([79393e8](https://github.com/payloadcms/payload/commit/79393e8cf0b79b31fa711536e0bc22b1a251468a))
### Features ### Features
* improve typing of ExtendableError and APIError ([#2864](https://github.com/payloadcms/payload/issues/2864)) ([7c47e4b](https://github.com/payloadcms/payload/commit/7c47e4b0d3c63f6f7800daaf424935d6067ffcc4)) - improve typing of ExtendableError and APIError ([#2864](https://github.com/payloadcms/payload/issues/2864)) ([7c47e4b](https://github.com/payloadcms/payload/commit/7c47e4b0d3c63f6f7800daaf424935d6067ffcc4))
* narrow endpoint.method type ([#1880](https://github.com/payloadcms/payload/issues/1880)) ([b734a1c](https://github.com/payloadcms/payload/commit/b734a1c422d200cad1085b7e92f8540df4238e32)) - narrow endpoint.method type ([#1880](https://github.com/payloadcms/payload/issues/1880)) ([b734a1c](https://github.com/payloadcms/payload/commit/b734a1c422d200cad1085b7e92f8540df4238e32))
## [1.10.5](https://github.com/payloadcms/payload/compare/v1.10.4...v1.10.5) (2023-06-30) ## [1.10.5](https://github.com/payloadcms/payload/compare/v1.10.4...v1.10.5) (2023-06-30)
### Bug Fixes ### Bug Fixes
* fields in drawer cannot be edited ([#2949](https://github.com/payloadcms/payload/issues/2949)) ([0c2e41c](https://github.com/payloadcms/payload/commit/0c2e41c4bef9333c47a9b1db0de807696b3f3872)), closes [#2945](https://github.com/payloadcms/payload/issues/2945) - fields in drawer cannot be edited ([#2949](https://github.com/payloadcms/payload/issues/2949)) ([0c2e41c](https://github.com/payloadcms/payload/commit/0c2e41c4bef9333c47a9b1db0de807696b3f3872)), closes [#2945](https://github.com/payloadcms/payload/issues/2945)
* improve versions test suite ([#2941](https://github.com/payloadcms/payload/issues/2941)) ([1d4df99](https://github.com/payloadcms/payload/commit/1d4df99ea78c5f682074ae824dcd8dea18b774e0)) - improve versions test suite ([#2941](https://github.com/payloadcms/payload/issues/2941)) ([1d4df99](https://github.com/payloadcms/payload/commit/1d4df99ea78c5f682074ae824dcd8dea18b774e0))
* incorrect graphql type generation ([#2898](https://github.com/payloadcms/payload/issues/2898)) ([b36deb4](https://github.com/payloadcms/payload/commit/b36deb4640cad4f494a12ab74b4e4d9a918cd94b)) - incorrect graphql type generation ([#2898](https://github.com/payloadcms/payload/issues/2898)) ([b36deb4](https://github.com/payloadcms/payload/commit/b36deb4640cad4f494a12ab74b4e4d9a918cd94b))
## [1.10.4](https://github.com/payloadcms/payload/compare/v1.10.3...v1.10.4) (2023-06-30) ## [1.10.4](https://github.com/payloadcms/payload/compare/v1.10.3...v1.10.4) (2023-06-30)
### Features ### Features
* add locale to displayed API URL ([b22d157](https://github.com/payloadcms/payload/commit/b22d157bd2f1c1a857e2d42bdc5b893549e3db9e)) - add locale to displayed API URL ([b22d157](https://github.com/payloadcms/payload/commit/b22d157bd2f1c1a857e2d42bdc5b893549e3db9e))
## [1.10.3](https://github.com/payloadcms/payload/compare/v1.10.2...v1.10.3) (2023-06-30) ## [1.10.3](https://github.com/payloadcms/payload/compare/v1.10.2...v1.10.3) (2023-06-30)
### Bug Fixes ### Bug Fixes
* [#2937](https://github.com/payloadcms/payload/issues/2937), depth not being respected in graphql rich text fields ([f84b432](https://github.com/payloadcms/payload/commit/f84b4323e2fce57e2e14b181e486ed72cc09ded5)) - [#2937](https://github.com/payloadcms/payload/issues/2937), depth not being respected in graphql rich text fields ([f84b432](https://github.com/payloadcms/payload/commit/f84b4323e2fce57e2e14b181e486ed72cc09ded5))
* shows updatedAt date when selecting a version to compare from dropdown ([3c9dab3](https://github.com/payloadcms/payload/commit/3c9dab3b9d5302d8bdf5792f0384cd5aeeb13839)) - shows updatedAt date when selecting a version to compare from dropdown ([3c9dab3](https://github.com/payloadcms/payload/commit/3c9dab3b9d5302d8bdf5792f0384cd5aeeb13839))
## [1.10.2](https://github.com/payloadcms/payload/compare/v1.10.1...v1.10.2) (2023-06-26) ## [1.10.2](https://github.com/payloadcms/payload/compare/v1.10.1...v1.10.2) (2023-06-26)
### Bug Fixes ### Bug Fixes
* adjusts swc loader to only exclude non ts/tsx files - [#2888](https://github.com/payloadcms/payload/issues/2888) ([#2907](https://github.com/payloadcms/payload/issues/2907)) ([a2d9ef3](https://github.com/payloadcms/payload/commit/a2d9ef3ca618934df58102a7e02e86dbe0ed63da)) - adjusts swc loader to only exclude non ts/tsx files - [#2888](https://github.com/payloadcms/payload/issues/2888) ([#2907](https://github.com/payloadcms/payload/issues/2907)) ([a2d9ef3](https://github.com/payloadcms/payload/commit/a2d9ef3ca618934df58102a7e02e86dbe0ed63da))
* autosave on localized fields, adds test ([6893231](https://github.com/payloadcms/payload/commit/6893231f85f702189089a6d78d3f3af63aaa0d82)) - autosave on localized fields, adds test ([6893231](https://github.com/payloadcms/payload/commit/6893231f85f702189089a6d78d3f3af63aaa0d82))
* broken export of entityToJSONSchema ([#2894](https://github.com/payloadcms/payload/issues/2894)) ([837dccc](https://github.com/payloadcms/payload/commit/837dcccefeffe7bb6e674713b4184c4eb92db8dc)) - broken export of entityToJSONSchema ([#2894](https://github.com/payloadcms/payload/issues/2894)) ([837dccc](https://github.com/payloadcms/payload/commit/837dcccefeffe7bb6e674713b4184c4eb92db8dc))
* correctly scopes data variable within bulk update - [#2901](https://github.com/payloadcms/payload/issues/2901) ([#2904](https://github.com/payloadcms/payload/issues/2904)) ([f627277](https://github.com/payloadcms/payload/commit/f627277479e6a4a847e79f54c545712a7186abb9)) - correctly scopes data variable within bulk update - [#2901](https://github.com/payloadcms/payload/issues/2901) ([#2904](https://github.com/payloadcms/payload/issues/2904)) ([f627277](https://github.com/payloadcms/payload/commit/f627277479e6a4a847e79f54c545712a7186abb9))
* safely check for tempFilePath when updating media document ([#2899](https://github.com/payloadcms/payload/issues/2899)) ([8206c0f](https://github.com/payloadcms/payload/commit/8206c0fe8be78a5e0f7c8e64996d73d135b1fcc2)) - safely check for tempFilePath when updating media document ([#2899](https://github.com/payloadcms/payload/issues/2899)) ([8206c0f](https://github.com/payloadcms/payload/commit/8206c0fe8be78a5e0f7c8e64996d73d135b1fcc2))
## [1.10.1](https://github.com/payloadcms/payload/compare/v1.10.0...v1.10.1) (2023-06-22) ## [1.10.1](https://github.com/payloadcms/payload/compare/v1.10.0...v1.10.1) (2023-06-22)
### Bug Fixes ### Bug Fixes
* conditional fields perf bug - [#2886](https://github.com/payloadcms/payload/issues/2886) ([#2890](https://github.com/payloadcms/payload/issues/2890)) ([b83d788](https://github.com/payloadcms/payload/commit/b83d788d3cfe12f87dcd63a9df20b939a6f4681e)) - conditional fields perf bug - [#2886](https://github.com/payloadcms/payload/issues/2886) ([#2890](https://github.com/payloadcms/payload/issues/2890)) ([b83d788](https://github.com/payloadcms/payload/commit/b83d788d3cfe12f87dcd63a9df20b939a6f4681e))
* cutoff tooltips in relationship field ([#2873](https://github.com/payloadcms/payload/issues/2873)) ([09c6cad](https://github.com/payloadcms/payload/commit/09c6cad3e8462dc3d8b1b6424aafd336c1d7828c)) - cutoff tooltips in relationship field ([#2873](https://github.com/payloadcms/payload/issues/2873)) ([09c6cad](https://github.com/payloadcms/payload/commit/09c6cad3e8462dc3d8b1b6424aafd336c1d7828c))
* Relationship hasMany and filterOptions fails above 10 items ([#2891](https://github.com/payloadcms/payload/issues/2891)) ([8128de6](https://github.com/payloadcms/payload/commit/8128de64dff98fdbcf053faef9de3c3f9a733071)) - Relationship hasMany and filterOptions fails above 10 items ([#2891](https://github.com/payloadcms/payload/issues/2891)) ([8128de6](https://github.com/payloadcms/payload/commit/8128de64dff98fdbcf053faef9de3c3f9a733071))
# [1.10.0](https://github.com/payloadcms/payload/compare/v1.9.5...v1.10.0) (2023-06-20) # [1.10.0](https://github.com/payloadcms/payload/compare/v1.9.5...v1.10.0) (2023-06-20)
### Bug Fixes ### Bug Fixes
* [#2831](https://github.com/payloadcms/payload/issues/2831), persists payloadAPI through local operations that accept req ([85d2467](https://github.com/payloadcms/payload/commit/85d2467d73582a372ee34e3ce93403847a1f0689)) - [#2831](https://github.com/payloadcms/payload/issues/2831), persists payloadAPI through local operations that accept req ([85d2467](https://github.com/payloadcms/payload/commit/85d2467d73582a372ee34e3ce93403847a1f0689))
* [#2842](https://github.com/payloadcms/payload/issues/2842), querying number custom ids with in ([116e9ff](https://github.com/payloadcms/payload/commit/116e9ffe81f44c4b40fa578b4a8fe4bb70fd110c)) - [#2842](https://github.com/payloadcms/payload/issues/2842), querying number custom ids with in ([116e9ff](https://github.com/payloadcms/payload/commit/116e9ffe81f44c4b40fa578b4a8fe4bb70fd110c))
* default sort with near operator ([#2862](https://github.com/payloadcms/payload/issues/2862)) ([99f3809](https://github.com/payloadcms/payload/commit/99f38098dd4a386437c469becc975ca86c54601f)) - default sort with near operator ([#2862](https://github.com/payloadcms/payload/issues/2862)) ([99f3809](https://github.com/payloadcms/payload/commit/99f38098dd4a386437c469becc975ca86c54601f))
* deprecate min/max in exchange for minRows and maxRows for relationship field ([#2826](https://github.com/payloadcms/payload/issues/2826)) ([0d8d7f3](https://github.com/payloadcms/payload/commit/0d8d7f358d390184f6f888d77858b4a145e94214)) - deprecate min/max in exchange for minRows and maxRows for relationship field ([#2826](https://github.com/payloadcms/payload/issues/2826)) ([0d8d7f3](https://github.com/payloadcms/payload/commit/0d8d7f358d390184f6f888d77858b4a145e94214))
* drawer close on backspace ([#2869](https://github.com/payloadcms/payload/issues/2869)) ([a110ba2](https://github.com/payloadcms/payload/commit/a110ba2dc09cd0824a9b1eb8e011604388277bd8)) - drawer close on backspace ([#2869](https://github.com/payloadcms/payload/issues/2869)) ([a110ba2](https://github.com/payloadcms/payload/commit/a110ba2dc09cd0824a9b1eb8e011604388277bd8))
* drawer fields are read-only if opened from a hasMany relationship ([#2843](https://github.com/payloadcms/payload/issues/2843)) ([542b536](https://github.com/payloadcms/payload/commit/542b5362d3ec8741aff6b1672fab7d2250e7b854)) - drawer fields are read-only if opened from a hasMany relationship ([#2843](https://github.com/payloadcms/payload/issues/2843)) ([542b536](https://github.com/payloadcms/payload/commit/542b5362d3ec8741aff6b1672fab7d2250e7b854))
* fields in relationship drawer not usable [#2815](https://github.com/payloadcms/payload/issues/2815) ([#2870](https://github.com/payloadcms/payload/issues/2870)) ([8626dc6](https://github.com/payloadcms/payload/commit/8626dc6b1a926143e7ba505f3edd924432168675)) - fields in relationship drawer not usable [#2815](https://github.com/payloadcms/payload/issues/2815) ([#2870](https://github.com/payloadcms/payload/issues/2870)) ([8626dc6](https://github.com/payloadcms/payload/commit/8626dc6b1a926143e7ba505f3edd924432168675))
* mobile loading overlay width [#2866](https://github.com/payloadcms/payload/issues/2866) ([#2867](https://github.com/payloadcms/payload/issues/2867)) ([ba9d633](https://github.com/payloadcms/payload/commit/ba9d6336acc779cfec0db312c8e2da912ce58cd4)) - mobile loading overlay width [#2866](https://github.com/payloadcms/payload/issues/2866) ([#2867](https://github.com/payloadcms/payload/issues/2867)) ([ba9d633](https://github.com/payloadcms/payload/commit/ba9d6336acc779cfec0db312c8e2da912ce58cd4))
* near query sorting by distance and pagination ([#2861](https://github.com/payloadcms/payload/issues/2861)) ([1611896](https://github.com/payloadcms/payload/commit/16118960aa6d63f7a429f168ff4305f336b1b1e6)) - near query sorting by distance and pagination ([#2861](https://github.com/payloadcms/payload/issues/2861)) ([1611896](https://github.com/payloadcms/payload/commit/16118960aa6d63f7a429f168ff4305f336b1b1e6))
* relationship field query pagination ([#2871](https://github.com/payloadcms/payload/issues/2871)) ([ce84174](https://github.com/payloadcms/payload/commit/ce84174554d9d828cbaaaa9548e5defc0feb4e2b)) - relationship field query pagination ([#2871](https://github.com/payloadcms/payload/issues/2871)) ([ce84174](https://github.com/payloadcms/payload/commit/ce84174554d9d828cbaaaa9548e5defc0feb4e2b))
* slow like queries with lots of records ([4dd703a](https://github.com/payloadcms/payload/commit/4dd703a6bff0ab7d06af234baa975553bd62f176)) - slow like queries with lots of records ([4dd703a](https://github.com/payloadcms/payload/commit/4dd703a6bff0ab7d06af234baa975553bd62f176))
### Features ### Features
* automatically redirect a user back to their originally requested URL after login ([#2838](https://github.com/payloadcms/payload/issues/2838)) ([e910688](https://github.com/payloadcms/payload/commit/e9106882f721d43bcc05a1690bda7754b450404e)) - automatically redirect a user back to their originally requested URL after login ([#2838](https://github.com/payloadcms/payload/issues/2838)) ([e910688](https://github.com/payloadcms/payload/commit/e9106882f721d43bcc05a1690bda7754b450404e))
* hasMany for number field ([#2517](https://github.com/payloadcms/payload/issues/2517)) ([8f086e3](https://github.com/payloadcms/payload/commit/8f086e315cb30be9d399fd3022c16952fb81cb2e)), closes [#2812](https://github.com/payloadcms/payload/issues/2812) [#2821](https://github.com/payloadcms/payload/issues/2821) [#2823](https://github.com/payloadcms/payload/issues/2823) [#2824](https://github.com/payloadcms/payload/issues/2824) [#2814](https://github.com/payloadcms/payload/issues/2814) [#2793](https://github.com/payloadcms/payload/issues/2793) [#2835](https://github.com/payloadcms/payload/issues/2835) - hasMany for number field ([#2517](https://github.com/payloadcms/payload/issues/2517)) ([8f086e3](https://github.com/payloadcms/payload/commit/8f086e315cb30be9d399fd3022c16952fb81cb2e)), closes [#2812](https://github.com/payloadcms/payload/issues/2812) [#2821](https://github.com/payloadcms/payload/issues/2821) [#2823](https://github.com/payloadcms/payload/issues/2823) [#2824](https://github.com/payloadcms/payload/issues/2824) [#2814](https://github.com/payloadcms/payload/issues/2814) [#2793](https://github.com/payloadcms/payload/issues/2793) [#2835](https://github.com/payloadcms/payload/issues/2835)
* optimizes conditional logic performance ([967f217](https://github.com/payloadcms/payload/commit/967f21734600de1fec8c1227a354ef5a417e54c5)) - optimizes conditional logic performance ([967f217](https://github.com/payloadcms/payload/commit/967f21734600de1fec8c1227a354ef5a417e54c5))
## [1.9.5](https://github.com/payloadcms/payload/compare/v1.9.4...v1.9.5) (2023-06-16) ## [1.9.5](https://github.com/payloadcms/payload/compare/v1.9.4...v1.9.5) (2023-06-16)
## [1.9.4](https://github.com/payloadcms/payload/compare/v1.9.3...v1.9.4) (2023-06-16) ## [1.9.4](https://github.com/payloadcms/payload/compare/v1.9.3...v1.9.4) (2023-06-16)
### Bug Fixes ### Bug Fixes
* incorrectly return totalDocs=1 instead of the correct count when pagination=false ([2e73938](https://github.com/payloadcms/payload/commit/2e7393853447d2da41ddef79f73e9026719a674b)) - incorrectly return totalDocs=1 instead of the correct count when pagination=false ([2e73938](https://github.com/payloadcms/payload/commit/2e7393853447d2da41ddef79f73e9026719a674b))
## [1.9.3](https://github.com/payloadcms/payload/compare/v1.9.2...v1.9.3) (2023-06-16) ## [1.9.3](https://github.com/payloadcms/payload/compare/v1.9.2...v1.9.3) (2023-06-16)
### Bug Fixes ### Bug Fixes
* adds custom property to ui field in joi validation ([#2835](https://github.com/payloadcms/payload/issues/2835)) ([56d7745](https://github.com/payloadcms/payload/commit/56d7745139e31c5d42c5191477f409f12589a952)) - adds custom property to ui field in joi validation ([#2835](https://github.com/payloadcms/payload/issues/2835)) ([56d7745](https://github.com/payloadcms/payload/commit/56d7745139e31c5d42c5191477f409f12589a952))
* ensures relations to object ids can be queried on ([c3d6e1b](https://github.com/payloadcms/payload/commit/c3d6e1b490a69f0aadb00e54e46a8774732e6658)) - ensures relations to object ids can be queried on ([c3d6e1b](https://github.com/payloadcms/payload/commit/c3d6e1b490a69f0aadb00e54e46a8774732e6658))
## [1.9.2](https://github.com/payloadcms/payload/compare/v1.9.1...v1.9.2) (2023-06-14) ## [1.9.2](https://github.com/payloadcms/payload/compare/v1.9.1...v1.9.2) (2023-06-14)
### Bug Fixes ### Bug Fixes
* [#2821](https://github.com/payloadcms/payload/issues/2821) i18n ui field label ([#2823](https://github.com/payloadcms/payload/issues/2823)) ([63cd7fb](https://github.com/payloadcms/payload/commit/63cd7fbd0c91bbf5120e95fd33388a38e593b341)) - [#2821](https://github.com/payloadcms/payload/issues/2821) i18n ui field label ([#2823](https://github.com/payloadcms/payload/issues/2823)) ([63cd7fb](https://github.com/payloadcms/payload/commit/63cd7fbd0c91bbf5120e95fd33388a38e593b341))
* adds missing dark-mode styles for version differences view ([#2812](https://github.com/payloadcms/payload/issues/2812)) ([346a48f](https://github.com/payloadcms/payload/commit/346a48f871e09a3d5e25b7ff9e45689a104b0f9f)) - adds missing dark-mode styles for version differences view ([#2812](https://github.com/payloadcms/payload/issues/2812)) ([346a48f](https://github.com/payloadcms/payload/commit/346a48f871e09a3d5e25b7ff9e45689a104b0f9f))
* sanitize reset password result - [#2805](https://github.com/payloadcms/payload/issues/2805) ([#2808](https://github.com/payloadcms/payload/issues/2808)) ([46a5f41](https://github.com/payloadcms/payload/commit/46a5f417217313b049f4b412abb3319634f27262)) - sanitize reset password result - [#2805](https://github.com/payloadcms/payload/issues/2805) ([#2808](https://github.com/payloadcms/payload/issues/2808)) ([46a5f41](https://github.com/payloadcms/payload/commit/46a5f417217313b049f4b412abb3319634f27262))
* user can be created without having to specify an email - [#2801](https://github.com/payloadcms/payload/issues/2801) ([abe3852](https://github.com/payloadcms/payload/commit/abe38520aaaefdfaea4c47130eea04a42a82627b)) - user can be created without having to specify an email - [#2801](https://github.com/payloadcms/payload/issues/2801) ([abe3852](https://github.com/payloadcms/payload/commit/abe38520aaaefdfaea4c47130eea04a42a82627b))
## [1.9.1](https://github.com/payloadcms/payload/compare/v1.9.0...v1.9.1) (2023-06-09) ## [1.9.1](https://github.com/payloadcms/payload/compare/v1.9.0...v1.9.1) (2023-06-09)
### Features ### Features
* adds option to customize filename on upload ([596eea1](https://github.com/payloadcms/payload/commit/596eea1f0a42628464e5269c496360b808c35f97)) - adds option to customize filename on upload ([596eea1](https://github.com/payloadcms/payload/commit/596eea1f0a42628464e5269c496360b808c35f97))
* collection list view custom components: BeforeList, BeforeListTable, AfterListTable, AfterList ([#2792](https://github.com/payloadcms/payload/issues/2792)) ([38e962f](https://github.com/payloadcms/payload/commit/38e962f2cbcaf9eaa72276969289efdbf670c7c7)) - collection list view custom components: BeforeList, BeforeListTable, AfterListTable, AfterList ([#2792](https://github.com/payloadcms/payload/issues/2792)) ([38e962f](https://github.com/payloadcms/payload/commit/38e962f2cbcaf9eaa72276969289efdbf670c7c7))
# [1.9.0](https://github.com/payloadcms/payload/compare/v1.8.6...v1.9.0) (2023-06-07) # [1.9.0](https://github.com/payloadcms/payload/compare/v1.8.6...v1.9.0) (2023-06-07)
### Features ### Features
* custom type interfaces ([#2709](https://github.com/payloadcms/payload/issues/2709)) ([8458a98](https://github.com/payloadcms/payload/commit/8458a98eff0eedf1abfd9ec065a084955a9b8149)) - custom type interfaces ([#2709](https://github.com/payloadcms/payload/issues/2709)) ([8458a98](https://github.com/payloadcms/payload/commit/8458a98eff0eedf1abfd9ec065a084955a9b8149))
## [1.8.6](https://github.com/payloadcms/payload/compare/v1.8.5...v1.8.6) (2023-06-07) ## [1.8.6](https://github.com/payloadcms/payload/compare/v1.8.5...v1.8.6) (2023-06-07)
### Bug Fixes ### Bug Fixes
* [#2711](https://github.com/payloadcms/payload/issues/2711) index sortable field global versions fields ([#2775](https://github.com/payloadcms/payload/issues/2775)) ([576af01](https://github.com/payloadcms/payload/commit/576af01b6f81d24621d522e8d8b9c496eafa6df0)) - [#2711](https://github.com/payloadcms/payload/issues/2711) index sortable field global versions fields ([#2775](https://github.com/payloadcms/payload/issues/2775)) ([576af01](https://github.com/payloadcms/payload/commit/576af01b6f81d24621d522e8d8b9c496eafa6df0))
* [#2767](https://github.com/payloadcms/payload/issues/2767) bulk operations missing locales in admin requests ([e30871a](https://github.com/payloadcms/payload/commit/e30871a96ff25f12401a3cc3bc5e12c064eeff3f)) - [#2767](https://github.com/payloadcms/payload/issues/2767) bulk operations missing locales in admin requests ([e30871a](https://github.com/payloadcms/payload/commit/e30871a96ff25f12401a3cc3bc5e12c064eeff3f))
* [#2771](https://github.com/payloadcms/payload/issues/2771) relationship field not querying all collections ([#2774](https://github.com/payloadcms/payload/issues/2774)) ([8b767a1](https://github.com/payloadcms/payload/commit/8b767a166aa16659d8880cc68da546251725b20b)) - [#2771](https://github.com/payloadcms/payload/issues/2771) relationship field not querying all collections ([#2774](https://github.com/payloadcms/payload/issues/2774)) ([8b767a1](https://github.com/payloadcms/payload/commit/8b767a166aa16659d8880cc68da546251725b20b))
* adjusts activation constraint of draggable nodes ([#2773](https://github.com/payloadcms/payload/issues/2773)) ([863be3d](https://github.com/payloadcms/payload/commit/863be3d852af6c6a76021695f895badf23e776ae)) - adjusts activation constraint of draggable nodes ([#2773](https://github.com/payloadcms/payload/issues/2773)) ([863be3d](https://github.com/payloadcms/payload/commit/863be3d852af6c6a76021695f895badf23e776ae))
* flattens relationships in the update operation for globals [#2766](https://github.com/payloadcms/payload/issues/2766) ([#2776](https://github.com/payloadcms/payload/issues/2776)) ([3677cf6](https://github.com/payloadcms/payload/commit/3677cf688d0e456c42068b4eab0086e64407d938)) - flattens relationships in the update operation for globals [#2766](https://github.com/payloadcms/payload/issues/2766) ([#2776](https://github.com/payloadcms/payload/issues/2776)) ([3677cf6](https://github.com/payloadcms/payload/commit/3677cf688d0e456c42068b4eab0086e64407d938))
* improperly typing optional arrays with required fields as required ([f1fc305](https://github.com/payloadcms/payload/commit/f1fc305ac443ecb247622bc89067b129e96146fc)) - improperly typing optional arrays with required fields as required ([f1fc305](https://github.com/payloadcms/payload/commit/f1fc305ac443ecb247622bc89067b129e96146fc))
* read-only Auth fields ([#2781](https://github.com/payloadcms/payload/issues/2781)) ([3c72f33](https://github.com/payloadcms/payload/commit/3c72f3303c57e88256266c343225157e0b081bba)) - read-only Auth fields ([#2781](https://github.com/payloadcms/payload/issues/2781)) ([3c72f33](https://github.com/payloadcms/payload/commit/3c72f3303c57e88256266c343225157e0b081bba))
* read-only Auth fields ([#2781](https://github.com/payloadcms/payload/issues/2781)) ([60f5522](https://github.com/payloadcms/payload/commit/60f5522e67acb353e6d5ce05f0012241c192d4b4)) - read-only Auth fields ([#2781](https://github.com/payloadcms/payload/issues/2781)) ([60f5522](https://github.com/payloadcms/payload/commit/60f5522e67acb353e6d5ce05f0012241c192d4b4))
* recursiveNestedPaths not merging existing fields when hoisting row/collapsible fields ([#2769](https://github.com/payloadcms/payload/issues/2769)) ([536d701](https://github.com/payloadcms/payload/commit/536d7017eebd5a8e14b2936c55a7fccc90d3f530)) - recursiveNestedPaths not merging existing fields when hoisting row/collapsible fields ([#2769](https://github.com/payloadcms/payload/issues/2769)) ([536d701](https://github.com/payloadcms/payload/commit/536d7017eebd5a8e14b2936c55a7fccc90d3f530))
## [1.8.5](https://github.com/payloadcms/payload/compare/v1.8.4...v1.8.5) (2023-06-03) ## [1.8.5](https://github.com/payloadcms/payload/compare/v1.8.4...v1.8.5) (2023-06-03)
### Features ### Features
* allows objectid through relationship validation ([42afa6b](https://github.com/payloadcms/payload/commit/42afa6b48aa924fa0dfc9defadf08ddb029da6c1)) - allows objectid through relationship validation ([42afa6b](https://github.com/payloadcms/payload/commit/42afa6b48aa924fa0dfc9defadf08ddb029da6c1))
## [1.8.4](https://github.com/payloadcms/payload/compare/v1.8.3...v1.8.4) (2023-06-02) ## [1.8.4](https://github.com/payloadcms/payload/compare/v1.8.3...v1.8.4) (2023-06-02)
### Features ### Features
* Add Bulgarian translation ([#2753](https://github.com/payloadcms/payload/issues/2753)) ([51108c0](https://github.com/payloadcms/payload/commit/51108c02ea346fd41c1b94ef7c339feec8383dd1)) - Add Bulgarian translation ([#2753](https://github.com/payloadcms/payload/issues/2753)) ([51108c0](https://github.com/payloadcms/payload/commit/51108c02ea346fd41c1b94ef7c339feec8383dd1))
### Bug Fixes ### Bug Fixes
* group row hoisting ([#2683](https://github.com/payloadcms/payload/issues/2683)) ([1626e17](https://github.com/payloadcms/payload/commit/1626e173b7eced83c59e8eb4f70b0bb68fdb0e7a)) - group row hoisting ([#2683](https://github.com/payloadcms/payload/issues/2683)) ([1626e17](https://github.com/payloadcms/payload/commit/1626e173b7eced83c59e8eb4f70b0bb68fdb0e7a))
* graphql where types on rows and collapsible's ([#2758](https://github.com/payloadcms/payload/issues/2758)) ([f978299](https://github.com/payloadcms/payload/commit/f978299868bf352e147070afdf556bf1153bac56)) - graphql where types on rows and collapsible's ([#2758](https://github.com/payloadcms/payload/issues/2758)) ([f978299](https://github.com/payloadcms/payload/commit/f978299868bf352e147070afdf556bf1153bac56))
* RichText link custom fields ([#2756](https://github.com/payloadcms/payload/issues/2756)) ([23be263](https://github.com/payloadcms/payload/commit/23be263dd2e75dca448019b1c66d7f6dd3558b37)) - RichText link custom fields ([#2756](https://github.com/payloadcms/payload/issues/2756)) ([23be263](https://github.com/payloadcms/payload/commit/23be263dd2e75dca448019b1c66d7f6dd3558b37))
* adds timestamps to global schemas ([#2738](https://github.com/payloadcms/payload/issues/2738)) ([0986282](https://github.com/payloadcms/payload/commit/0986282f13d8a3b5596c4a241b4da35e6fac6aa1)) - adds timestamps to global schemas ([#2738](https://github.com/payloadcms/payload/issues/2738)) ([0986282](https://github.com/payloadcms/payload/commit/0986282f13d8a3b5596c4a241b4da35e6fac6aa1))
* adjusts code field joi schema to allow editorOptions ([ed136fb](https://github.com/payloadcms/payload/commit/ed136fbc5146889cd30c641d4947da58b66dfb2f)) - adjusts code field joi schema to allow editorOptions ([ed136fb](https://github.com/payloadcms/payload/commit/ed136fbc5146889cd30c641d4947da58b66dfb2f))
* fix locale popup overflow ([#2737](https://github.com/payloadcms/payload/issues/2737)) ([8ee9724](https://github.com/payloadcms/payload/commit/8ee9724277d419de78b27a8ffa22f3a599361251)) - fix locale popup overflow ([#2737](https://github.com/payloadcms/payload/issues/2737)) ([8ee9724](https://github.com/payloadcms/payload/commit/8ee9724277d419de78b27a8ffa22f3a599361251))
* fix tests by hard-coding the URL in the logger ([2697974](https://github.com/payloadcms/payload/commit/2697974694112440bf1737c4ce535ba77bf4b194)) - fix tests by hard-coding the URL in the logger ([2697974](https://github.com/payloadcms/payload/commit/2697974694112440bf1737c4ce535ba77bf4b194))
* mongoose connection ([#2754](https://github.com/payloadcms/payload/issues/2754)) ([69b97bb](https://github.com/payloadcms/payload/commit/69b97bbc590c62fffbcd03a42f0e9737e3f7ca01)) - mongoose connection ([#2754](https://github.com/payloadcms/payload/issues/2754)) ([69b97bb](https://github.com/payloadcms/payload/commit/69b97bbc590c62fffbcd03a42f0e9737e3f7ca01))
* removes payload dependency inception ([#2717](https://github.com/payloadcms/payload/issues/2717)) ([6125b66](https://github.com/payloadcms/payload/commit/6125b66286e5315725ca0ae365c81a04c1c1a54c)) - removes payload dependency inception ([#2717](https://github.com/payloadcms/payload/issues/2717)) ([6125b66](https://github.com/payloadcms/payload/commit/6125b66286e5315725ca0ae365c81a04c1c1a54c))
* searches on correct useAsTitle field in polymorphic list drawers [#2710](https://github.com/payloadcms/payload/issues/2710) ([9ec2a40](https://github.com/payloadcms/payload/commit/9ec2a40274ea9b3a32e43cb992df3897baf62e63)) - searches on correct useAsTitle field in polymorphic list drawers [#2710](https://github.com/payloadcms/payload/issues/2710) ([9ec2a40](https://github.com/payloadcms/payload/commit/9ec2a40274ea9b3a32e43cb992df3897baf62e63))
* typing of sendMail function ([e3ff4c4](https://github.com/payloadcms/payload/commit/e3ff4c46cbecf731c9a3c688682bcb33012cb234)) - typing of sendMail function ([e3ff4c4](https://github.com/payloadcms/payload/commit/e3ff4c46cbecf731c9a3c688682bcb33012cb234))
* corrects relationship field schema from pr [#2696](https://github.com/payloadcms/payload/issues/2696) ([#2714](https://github.com/payloadcms/payload/issues/2714)) ([8285bac](https://github.com/payloadcms/payload/commit/8285bac2f5eb443b6af160b21726edf3f828a52f)) - corrects relationship field schema from pr [#2696](https://github.com/payloadcms/payload/issues/2696) ([#2714](https://github.com/payloadcms/payload/issues/2714)) ([8285bac](https://github.com/payloadcms/payload/commit/8285bac2f5eb443b6af160b21726edf3f828a52f))
## [1.8.3](https://github.com/payloadcms/payload/compare/v1.8.3...v1.8.3) (2023-05-24) ## [1.8.3](https://github.com/payloadcms/payload/compare/v1.8.3...v1.8.3) (2023-05-24)
### Bug Fixes ### Bug Fixes
* [#2662](https://github.com/payloadcms/payload/issues/2662), draft=true querying by id ([3b78ab0](https://github.com/payloadcms/payload/commit/3b78ab04c7a68e39afa9936ac692169ed2c8fb74)) - [#2662](https://github.com/payloadcms/payload/issues/2662), draft=true querying by id ([3b78ab0](https://github.com/payloadcms/payload/commit/3b78ab04c7a68e39afa9936ac692169ed2c8fb74))
* [#2685](https://github.com/payloadcms/payload/issues/2685), graphql querying relationships with custom id ([9bb5470](https://github.com/payloadcms/payload/commit/9bb54703423b3f0fdb242a5e63f322d346323b06)) - [#2685](https://github.com/payloadcms/payload/issues/2685), graphql querying relationships with custom id ([9bb5470](https://github.com/payloadcms/payload/commit/9bb54703423b3f0fdb242a5e63f322d346323b06))
* adds credentials to doc access request ([#2705](https://github.com/payloadcms/payload/issues/2705)) ([c716954](https://github.com/payloadcms/payload/commit/c716954e89b0aef976cbcbef9ece981ec9bab233)) - adds credentials to doc access request ([#2705](https://github.com/payloadcms/payload/issues/2705)) ([c716954](https://github.com/payloadcms/payload/commit/c716954e89b0aef976cbcbef9ece981ec9bab233))
* prevents add new relationship modal from adding duplicative values to the parent doc [#2688](https://github.com/payloadcms/payload/issues/2688) ([a2a8ac9](https://github.com/payloadcms/payload/commit/a2a8ac9549bd67e6ab578772689684fd2bc64872)) - prevents add new relationship modal from adding duplicative values to the parent doc [#2688](https://github.com/payloadcms/payload/issues/2688) ([a2a8ac9](https://github.com/payloadcms/payload/commit/a2a8ac9549bd67e6ab578772689684fd2bc64872))
* unable to clear relationships or open relationship drawer on mobile [#2691](https://github.com/payloadcms/payload/issues/2691) [#2692](https://github.com/payloadcms/payload/issues/2692) ([782f8ca](https://github.com/payloadcms/payload/commit/782f8ca047178cadb4214702854a0e0cb2d9eaab)) - unable to clear relationships or open relationship drawer on mobile [#2691](https://github.com/payloadcms/payload/issues/2691) [#2692](https://github.com/payloadcms/payload/issues/2692) ([782f8ca](https://github.com/payloadcms/payload/commit/782f8ca047178cadb4214702854a0e0cb2d9eaab))
## [1.8.2](https://github.com/payloadcms/payload/compare/v1.8.1...v1.8.2) (2023-05-10) ## [1.8.2](https://github.com/payloadcms/payload/compare/v1.8.1...v1.8.2) (2023-05-10)
### Bug Fixes ### Bug Fixes
* react webpack alias ([1732bb8](https://github.com/payloadcms/payload/commit/1732bb877ca9688fc87cf44fbf63d05b6be23de2)) - react webpack alias ([1732bb8](https://github.com/payloadcms/payload/commit/1732bb877ca9688fc87cf44fbf63d05b6be23de2))
## [1.8.1](https://github.com/payloadcms/payload/compare/v1.8.0...v1.8.1) (2023-05-10) ## [1.8.1](https://github.com/payloadcms/payload/compare/v1.8.0...v1.8.1) (2023-05-10)
### Bug Fixes ### Bug Fixes
* add dotenv.config() to test/dev.ts ([#2646](https://github.com/payloadcms/payload/issues/2646)) ([7963e75](https://github.com/payloadcms/payload/commit/7963e7540f4899c16a49b47cf5145f46ea0c71cf)) - add dotenv.config() to test/dev.ts ([#2646](https://github.com/payloadcms/payload/issues/2646)) ([7963e75](https://github.com/payloadcms/payload/commit/7963e7540f4899c16a49b47cf5145f46ea0c71cf))
### Features ### Features
* allow users to manipulate images without needing to resize them ([#2574](https://github.com/payloadcms/payload/issues/2574)) ([8531687](https://github.com/payloadcms/payload/commit/85316879cd97933ed34588b0cee72798964de281)) - allow users to manipulate images without needing to resize them ([#2574](https://github.com/payloadcms/payload/issues/2574)) ([8531687](https://github.com/payloadcms/payload/commit/85316879cd97933ed34588b0cee72798964de281))
* export additional graphql types ([#2610](https://github.com/payloadcms/payload/issues/2610)) ([3f185cb](https://github.com/payloadcms/payload/commit/3f185cb18b9677654b92921267ffef408388d0d1)) - export additional graphql types ([#2610](https://github.com/payloadcms/payload/issues/2610)) ([3f185cb](https://github.com/payloadcms/payload/commit/3f185cb18b9677654b92921267ffef408388d0d1))
# [1.8.0](https://github.com/payloadcms/payload/compare/v1.7.5...v1.8.0) (2023-05-09) # [1.8.0](https://github.com/payloadcms/payload/compare/v1.7.5...v1.8.0) (2023-05-09)
### Bug Fixes ### Bug Fixes
* correct casing on graphql type ([219f50b](https://github.com/payloadcms/payload/commit/219f50b0bc7a520655a5ae4f1d8b08fd04c8a3dd)) - correct casing on graphql type ([219f50b](https://github.com/payloadcms/payload/commit/219f50b0bc7a520655a5ae4f1d8b08fd04c8a3dd))
* defaultValue missing from Upload field schema ([7b21eaf](https://github.com/payloadcms/payload/commit/7b21eaf12da64778568b45e56fa8d39e81f11c29)) - defaultValue missing from Upload field schema ([7b21eaf](https://github.com/payloadcms/payload/commit/7b21eaf12da64778568b45e56fa8d39e81f11c29))
* ensures nested querying works when querying across collections ([09974fa](https://github.com/payloadcms/payload/commit/09974fa68677586c727943cc234311f87bf6da75)) - ensures nested querying works when querying across collections ([09974fa](https://github.com/payloadcms/payload/commit/09974fa68677586c727943cc234311f87bf6da75))
* query custom text id fields ([967f2ac](https://github.com/payloadcms/payload/commit/967f2ace0ea1a65570f69e85920f2f55626efde0)) - query custom text id fields ([967f2ac](https://github.com/payloadcms/payload/commit/967f2ace0ea1a65570f69e85920f2f55626efde0))
* removes deprecated queryHiddenFIelds from local API docs ([5f30dbb](https://github.com/payloadcms/payload/commit/5f30dbb1a5b7c7ab6752c114710f92c159319d3d)) - removes deprecated queryHiddenFIelds from local API docs ([5f30dbb](https://github.com/payloadcms/payload/commit/5f30dbb1a5b7c7ab6752c114710f92c159319d3d))
* removes queryHiddenFields from example Find operation ([fb4f822](https://github.com/payloadcms/payload/commit/fb4f822d34d0235a537f96515073e2662680412f)) - removes queryHiddenFields from example Find operation ([fb4f822](https://github.com/payloadcms/payload/commit/fb4f822d34d0235a537f96515073e2662680412f))
* resolve process/browser package in webpack config ([02f27f3](https://github.com/payloadcms/payload/commit/02f27f3de6fdaf5dd0023298fc671a8ae9a1b758)) - resolve process/browser package in webpack config ([02f27f3](https://github.com/payloadcms/payload/commit/02f27f3de6fdaf5dd0023298fc671a8ae9a1b758))
* Row groups in tabs vertical alignment ([#2593](https://github.com/payloadcms/payload/issues/2593)) ([54fac4a](https://github.com/payloadcms/payload/commit/54fac4a5d793b534e25600d2f9470c449f40df1d)) - Row groups in tabs vertical alignment ([#2593](https://github.com/payloadcms/payload/issues/2593)) ([54fac4a](https://github.com/payloadcms/payload/commit/54fac4a5d793b534e25600d2f9470c449f40df1d))
* softens columns and filters pill colors ([#2642](https://github.com/payloadcms/payload/issues/2642)) ([9072096](https://github.com/payloadcms/payload/commit/90720964953d392d85982052b3a4843a5450681e)) - softens columns and filters pill colors ([#2642](https://github.com/payloadcms/payload/issues/2642)) ([9072096](https://github.com/payloadcms/payload/commit/90720964953d392d85982052b3a4843a5450681e))
* webp upload formatting ([ccd6ca2](https://github.com/payloadcms/payload/commit/ccd6ca298e69faf04709535df3fcb18eb3d40f1b)) - webp upload formatting ([ccd6ca2](https://github.com/payloadcms/payload/commit/ccd6ca298e69faf04709535df3fcb18eb3d40f1b))
### Features ### Features
* add Arabic translations ([#2641](https://github.com/payloadcms/payload/issues/2641)) ([7d04cf1](https://github.com/payloadcms/payload/commit/7d04cf14fb0587f2208745bb77ed4fd17e99c8d5)) - add Arabic translations ([#2641](https://github.com/payloadcms/payload/issues/2641)) ([7d04cf1](https://github.com/payloadcms/payload/commit/7d04cf14fb0587f2208745bb77ed4fd17e99c8d5))
* allow full URL in staticURL ([#2562](https://github.com/payloadcms/payload/issues/2562)) ([a9b5dff](https://github.com/payloadcms/payload/commit/a9b5dffa00623eb48302d51b88c3449920c10f46)) - allow full URL in staticURL ([#2562](https://github.com/payloadcms/payload/issues/2562)) ([a9b5dff](https://github.com/payloadcms/payload/commit/a9b5dffa00623eb48302d51b88c3449920c10f46))
## [1.7.5](https://github.com/payloadcms/payload/compare/v1.7.4...v1.7.5) (2023-05-04) ## [1.7.5](https://github.com/payloadcms/payload/compare/v1.7.4...v1.7.5) (2023-05-04)
### Bug Fixes ### Bug Fixes
* make incrementName match multiple digits ([#2609](https://github.com/payloadcms/payload/issues/2609)) ([8dbf0a2](https://github.com/payloadcms/payload/commit/8dbf0a2bd88db1b361ce16bb730613de489f2ed2)) - make incrementName match multiple digits ([#2609](https://github.com/payloadcms/payload/issues/2609)) ([8dbf0a2](https://github.com/payloadcms/payload/commit/8dbf0a2bd88db1b361ce16bb730613de489f2ed2))
### Features ### Features
* collection admin.enableRichTextLink property ([#2560](https://github.com/payloadcms/payload/issues/2560)) ([9678992](https://github.com/payloadcms/payload/commit/967899229f458d06a3931d086bcc49299dc310b7)) - collection admin.enableRichTextLink property ([#2560](https://github.com/payloadcms/payload/issues/2560)) ([9678992](https://github.com/payloadcms/payload/commit/967899229f458d06a3931d086bcc49299dc310b7))
* custom admin buttons ([#2618](https://github.com/payloadcms/payload/issues/2618)) ([1d58007](https://github.com/payloadcms/payload/commit/1d58007606fa7e34007f2a56a3ca653d2cd3404d)) - custom admin buttons ([#2618](https://github.com/payloadcms/payload/issues/2618)) ([1d58007](https://github.com/payloadcms/payload/commit/1d58007606fa7e34007f2a56a3ca653d2cd3404d))
## [1.7.4](https://github.com/payloadcms/payload/compare/v1.7.3...v1.7.4) (2023-05-02) ## [1.7.4](https://github.com/payloadcms/payload/compare/v1.7.3...v1.7.4) (2023-05-02)
### Bug Fixes ### Bug Fixes
* properly import SwcMinifyWebpackPlugin ([#2600](https://github.com/payloadcms/payload/issues/2600)) ([802deac](https://github.com/payloadcms/payload/commit/802deaca03f8506fa4a7adb8fc008205c2c4f013)) - properly import SwcMinifyWebpackPlugin ([#2600](https://github.com/payloadcms/payload/issues/2600)) ([802deac](https://github.com/payloadcms/payload/commit/802deaca03f8506fa4a7adb8fc008205c2c4f013))
## [1.7.3](https://github.com/payloadcms/payload/compare/v1.7.2...v1.7.3) (2023-05-01) ## [1.7.3](https://github.com/payloadcms/payload/compare/v1.7.2...v1.7.3) (2023-05-01)
### Bug Fixes ### Bug Fixes
* [#2592](https://github.com/payloadcms/payload/issues/2592), allows usage of hidden fields within access query constraints ([#2599](https://github.com/payloadcms/payload/issues/2599)) ([a0bb13a](https://github.com/payloadcms/payload/commit/a0bb13a4123b51d770b364ddaee3dde1c5a3da53)) - [#2592](https://github.com/payloadcms/payload/issues/2592), allows usage of hidden fields within access query constraints ([#2599](https://github.com/payloadcms/payload/issues/2599)) ([a0bb13a](https://github.com/payloadcms/payload/commit/a0bb13a4123b51d770b364ddaee3dde1c5a3da53))
* addds workaround for slate isBlock function issue ([#2596](https://github.com/payloadcms/payload/issues/2596)) ([8f6f13d](https://github.com/payloadcms/payload/commit/8f6f13dc93f49f5ba5384a9168ced5baec85e1fb)) - addds workaround for slate isBlock function issue ([#2596](https://github.com/payloadcms/payload/issues/2596)) ([8f6f13d](https://github.com/payloadcms/payload/commit/8f6f13dc93f49f5ba5384a9168ced5baec85e1fb))
* bulk operations result type ([#2588](https://github.com/payloadcms/payload/issues/2588)) ([8382faa](https://github.com/payloadcms/payload/commit/8382faa0afc8118f4fb873c657a52c48abb2a6ad)) - bulk operations result type ([#2588](https://github.com/payloadcms/payload/issues/2588)) ([8382faa](https://github.com/payloadcms/payload/commit/8382faa0afc8118f4fb873c657a52c48abb2a6ad))
* query on id throws 500 ([#2587](https://github.com/payloadcms/payload/issues/2587)) ([0ba22c3](https://github.com/payloadcms/payload/commit/0ba22c3aafca67be78814357edc668ed11ec4a97)) - query on id throws 500 ([#2587](https://github.com/payloadcms/payload/issues/2587)) ([0ba22c3](https://github.com/payloadcms/payload/commit/0ba22c3aafca67be78814357edc668ed11ec4a97))
* timestamp queries ([#2583](https://github.com/payloadcms/payload/issues/2583)) ([9c5107e](https://github.com/payloadcms/payload/commit/9c5107e86d70e36ac181c9d3ad51edacf9fc529a)) - timestamp queries ([#2583](https://github.com/payloadcms/payload/issues/2583)) ([9c5107e](https://github.com/payloadcms/payload/commit/9c5107e86d70e36ac181c9d3ad51edacf9fc529a))
### Features ### Features
* Add new translation for romanian language ([#2556](https://github.com/payloadcms/payload/issues/2556)) ([fbf3a2a](https://github.com/payloadcms/payload/commit/fbf3a2a1b4633e704e467d9aec05f3ae0b900bae)) - Add new translation for romanian language ([#2556](https://github.com/payloadcms/payload/issues/2556)) ([fbf3a2a](https://github.com/payloadcms/payload/commit/fbf3a2a1b4633e704e467d9aec05f3ae0b900bae))
* add persian translations ([#2553](https://github.com/payloadcms/payload/issues/2553)) ([c80f68a](https://github.com/payloadcms/payload/commit/c80f68af943c730996c9cdad87cf84d4d06a5777)) - add persian translations ([#2553](https://github.com/payloadcms/payload/issues/2553)) ([c80f68a](https://github.com/payloadcms/payload/commit/c80f68af943c730996c9cdad87cf84d4d06a5777))
* adjust stack trace for api error ([#2598](https://github.com/payloadcms/payload/issues/2598)) ([870838e](https://github.com/payloadcms/payload/commit/870838e7563b6767c53f4dc0288119087e3f9486)) - adjust stack trace for api error ([#2598](https://github.com/payloadcms/payload/issues/2598)) ([870838e](https://github.com/payloadcms/payload/commit/870838e7563b6767c53f4dc0288119087e3f9486))
* allow customizing the link fields ([#2559](https://github.com/payloadcms/payload/issues/2559)) ([bf65228](https://github.com/payloadcms/payload/commit/bf6522898db353e75db11525ea5a1b58243333d8)) - allow customizing the link fields ([#2559](https://github.com/payloadcms/payload/issues/2559)) ([bf65228](https://github.com/payloadcms/payload/commit/bf6522898db353e75db11525ea5a1b58243333d8))
* supports collection compound indexes ([#2529](https://github.com/payloadcms/payload/issues/2529)) ([85b3d57](https://github.com/payloadcms/payload/commit/85b3d579d3054aad2de793957cf6454332361327)) - supports collection compound indexes ([#2529](https://github.com/payloadcms/payload/issues/2529)) ([85b3d57](https://github.com/payloadcms/payload/commit/85b3d579d3054aad2de793957cf6454332361327))
## [1.7.2](https://github.com/payloadcms/payload/compare/v1.7.1...v1.7.2) (2023-04-25) ## [1.7.2](https://github.com/payloadcms/payload/compare/v1.7.1...v1.7.2) (2023-04-25)
### Bug Fixes ### Bug Fixes
* [#2521](https://github.com/payloadcms/payload/issues/2521), graphql AND not working with drafts ([e67ca20](https://github.com/payloadcms/payload/commit/e67ca2010831c14938d3f639fcb5374ca62747ba)) - [#2521](https://github.com/payloadcms/payload/issues/2521), graphql AND not working with drafts ([e67ca20](https://github.com/payloadcms/payload/commit/e67ca2010831c14938d3f639fcb5374ca62747ba))
* document drawer access control [#2545](https://github.com/payloadcms/payload/issues/2545) ([439caf8](https://github.com/payloadcms/payload/commit/439caf815fc99538f14b3a59835dcf49185759dc)) - document drawer access control [#2545](https://github.com/payloadcms/payload/issues/2545) ([439caf8](https://github.com/payloadcms/payload/commit/439caf815fc99538f14b3a59835dcf49185759dc))
* prevent floating point number in image sizes ([#1935](https://github.com/payloadcms/payload/issues/1935)) ([7fcde11](https://github.com/payloadcms/payload/commit/7fcde11fa0b232537de606e44c0af68b122daed2)) - prevent floating point number in image sizes ([#1935](https://github.com/payloadcms/payload/issues/1935)) ([7fcde11](https://github.com/payloadcms/payload/commit/7fcde11fa0b232537de606e44c0af68b122daed2))
* prevent sharp toFormat settings fallthrough by using clone ([#2547](https://github.com/payloadcms/payload/issues/2547)) ([90dab3c](https://github.com/payloadcms/payload/commit/90dab3c445d4bdbab0eff286a2b66861d04f2a93)) - prevent sharp toFormat settings fallthrough by using clone ([#2547](https://github.com/payloadcms/payload/issues/2547)) ([90dab3c](https://github.com/payloadcms/payload/commit/90dab3c445d4bdbab0eff286a2b66861d04f2a93))
* query localized fields without localization configured ([12edb1c](https://github.com/payloadcms/payload/commit/12edb1cc4b2675d9b0948fb7f3439f61c6e2015d)) - query localized fields without localization configured ([12edb1c](https://github.com/payloadcms/payload/commit/12edb1cc4b2675d9b0948fb7f3439f61c6e2015d))
* read-only styles ([823d022](https://github.com/payloadcms/payload/commit/823d0228c949fe58a7e0f11f95354b240c3ea876)) - read-only styles ([823d022](https://github.com/payloadcms/payload/commit/823d0228c949fe58a7e0f11f95354b240c3ea876))
### Features ### Features
* add rich-text blockquote element, change quote node type to blockquote ([ed230a4](https://github.com/payloadcms/payload/commit/ed230a42e0315dc2492b4a26e3bf8b5334e89380)) - add rich-text blockquote element, change quote node type to blockquote ([ed230a4](https://github.com/payloadcms/payload/commit/ed230a42e0315dc2492b4a26e3bf8b5334e89380))
* add user to field conditional logic ([274edc7](https://github.com/payloadcms/payload/commit/274edc74a70202e8c771c5111507b585c3f69377)) - add user to field conditional logic ([274edc7](https://github.com/payloadcms/payload/commit/274edc74a70202e8c771c5111507b585c3f69377))
* exposes id in conditional logic ([c117b32](https://github.com/payloadcms/payload/commit/c117b321474b8318c3a0ddf544e49568e461f0d8)) - exposes id in conditional logic ([c117b32](https://github.com/payloadcms/payload/commit/c117b321474b8318c3a0ddf544e49568e461f0d8))
* **imageresizer:** add trim options ([#2073](https://github.com/payloadcms/payload/issues/2073)) ([0406548](https://github.com/payloadcms/payload/commit/0406548fe6127e091db9926ee42e59f9158eff5a)) - **imageresizer:** add trim options ([#2073](https://github.com/payloadcms/payload/issues/2073)) ([0406548](https://github.com/payloadcms/payload/commit/0406548fe6127e091db9926ee42e59f9158eff5a))
## [1.7.1](https://github.com/payloadcms/payload/compare/v1.7.0...v1.7.1) (2023-04-18) ## [1.7.1](https://github.com/payloadcms/payload/compare/v1.7.0...v1.7.1) (2023-04-18)
### Bug Fixes ### Bug Fixes
* adds 'use client' for next 13 compatibility ([5e02985](https://github.com/payloadcms/payload/commit/5e029852060d6475eccada35ffbcdd0178d5e690)) - adds 'use client' for next 13 compatibility ([5e02985](https://github.com/payloadcms/payload/commit/5e029852060d6475eccada35ffbcdd0178d5e690))
* graphql variables not being passed properly ([72be80a](https://github.com/payloadcms/payload/commit/72be80abc4082013e052aef1152a5de749a6f3c4)) - graphql variables not being passed properly ([72be80a](https://github.com/payloadcms/payload/commit/72be80abc4082013e052aef1152a5de749a6f3c4))
### Features ### Features
* configuration extension points ([023719d](https://github.com/payloadcms/payload/commit/023719d77554a70493d779ba94bf55058d4caf98)) - configuration extension points ([023719d](https://github.com/payloadcms/payload/commit/023719d77554a70493d779ba94bf55058d4caf98))
## [1.7.0](https://github.com/payloadcms/payload/compare/v1.6.32...v1.7.0) (2023-04-17) ## [1.7.0](https://github.com/payloadcms/payload/compare/v1.6.32...v1.7.0) (2023-04-17)
@@ -829,47 +774,43 @@ We are pulling off a bandaid here and enforcing that `payload.init` is now async
To migrate, you need to convert your code everywhere that you run `payload.init` to be asynchronous instead. For example, here is an example of a traditional `payload.init` call which needs to be migrated: To migrate, you need to convert your code everywhere that you run `payload.init` to be asynchronous instead. For example, here is an example of a traditional `payload.init` call which needs to be migrated:
```js ```js
const express = require("express"); const express = require('express')
const payload = require("payload"); const payload = require('payload')
const app = express(); const app = express()
payload.init({ payload.init({
secret: "SECRET_KEY", secret: 'SECRET_KEY',
mongoURL: "mongodb://localhost/payload", mongoURL: 'mongodb://localhost/payload',
express: app, express: app,
}); })
app.listen(3000, async () => { app.listen(3000, async () => {
console.log( console.log('Express is now listening for incoming connections on port 3000.')
"Express is now listening for incoming connections on port 3000." })
);
});
``` ```
Your `payload.init` call will need to be converted into the following: Your `payload.init` call will need to be converted into the following:
```js ```js
const express = require("express"); const express = require('express')
const payload = require("payload"); const payload = require('payload')
const app = express(); const app = express()
const start = async () => { const start = async () => {
await payload.init({ await payload.init({
secret: "SECRET_KEY", secret: 'SECRET_KEY',
mongoURL: "mongodb://localhost/payload", mongoURL: 'mongodb://localhost/payload',
express: app, express: app,
}); })
app.listen(3000, async () => { app.listen(3000, async () => {
console.log( console.log('Express is now listening for incoming connections on port 3000.')
"Express is now listening for incoming connections on port 3000." })
); }
});
};
start(); start()
``` ```
Notice that all we've done is wrapped the `payload.init` and `app.listen` calls with a `start` function that is asynchronous. Notice that all we've done is wrapped the `payload.init` and `app.listen` calls with a `start` function that is asynchronous.
@@ -880,18 +821,18 @@ Before this release, the Local API methods were configured as generics. For exam
```ts ```ts
const post = await payload.findByID<Post>({ const post = await payload.findByID<Post>({
collection: "posts", collection: 'posts',
id: "id-of-post-here", id: 'id-of-post-here',
}); })
``` ```
Now, you don't need to pass your types and Payload will automatically infer them for you, as well as significantly improve typing throughout the local API. Here's an example: Now, you don't need to pass your types and Payload will automatically infer them for you, as well as significantly improve typing throughout the local API. Here's an example:
```ts ```ts
const post = await payload.findByID({ const post = await payload.findByID({
collection: "posts", // this is now auto-typed collection: 'posts', // this is now auto-typed
id: "id-of-post-here", id: 'id-of-post-here',
}); })
// `post` will be automatically typed as `Post` // `post` will be automatically typed as `Post`
``` ```
@@ -938,11 +879,11 @@ To migrate, create this file within the root of your Payload project:
**migrateVersions.ts** **migrateVersions.ts**
```ts ```ts
const payload = require("payload"); const payload = require('payload')
require("dotenv").config(); require('dotenv').config()
const { PAYLOAD_SECRET, MONGODB_URI } = process.env; const { PAYLOAD_SECRET, MONGODB_URI } = process.env
// This function ensures that there is at least one corresponding version for any document // This function ensures that there is at least one corresponding version for any document
// within each of your draft-enabled collections. // within each of your draft-enabled collections.
@@ -955,7 +896,7 @@ const ensureAtLeastOneVersion = async () => {
secret: PAYLOAD_SECRET, secret: PAYLOAD_SECRET,
mongoURL: MONGODB_URI, mongoURL: MONGODB_URI,
local: true, local: true,
}); })
// For each collection // For each collection
await Promise.all( await Promise.all(
@@ -966,14 +907,14 @@ const ensureAtLeastOneVersion = async () => {
collection: slug, collection: slug,
limit: 0, limit: 0,
depth: 0, depth: 0,
locale: "all", locale: 'all',
}); })
const VersionsModel = payload.versions[slug]; const VersionsModel = payload.versions[slug]
const existingCollectionDocIds: Array<string> = []; const existingCollectionDocIds: Array<string> = []
await Promise.all( await Promise.all(
docs.map(async (doc) => { docs.map(async (doc) => {
existingCollectionDocIds.push(doc.id); existingCollectionDocIds.push(doc.id)
// Find at least one version for the doc // Find at least one version for the doc
const versionDocs = await VersionsModel.find( const versionDocs = await VersionsModel.find(
{ {
@@ -981,8 +922,8 @@ const ensureAtLeastOneVersion = async () => {
updatedAt: { $gte: doc.updatedAt }, updatedAt: { $gte: doc.updatedAt },
}, },
null, null,
{ limit: 1 } { limit: 1 },
).lean(); ).lean()
// If there are no corresponding versions, // If there are no corresponding versions,
// we need to create one // we need to create one
@@ -994,39 +935,37 @@ const ensureAtLeastOneVersion = async () => {
autosave: Boolean(versions?.drafts?.autosave), autosave: Boolean(versions?.drafts?.autosave),
updatedAt: doc.updatedAt, updatedAt: doc.updatedAt,
createdAt: doc.createdAt, createdAt: doc.createdAt,
}); })
} catch (e) { } catch (e) {
console.error( console.error(
`Unable to create version corresponding with collection ${slug} document ID ${doc.id}`, `Unable to create version corresponding with collection ${slug} document ID ${doc.id}`,
e?.errors || e e?.errors || e,
); )
} }
console.log( console.log(`Created version corresponding with ${slug} document ID ${doc.id}`)
`Created version corresponding with ${slug} document ID ${doc.id}`
);
} }
}) }),
); )
const versionsWithoutParentDocs = await VersionsModel.deleteMany({ const versionsWithoutParentDocs = await VersionsModel.deleteMany({
parent: { $nin: existingCollectionDocIds }, parent: { $nin: existingCollectionDocIds },
}); })
if (versionsWithoutParentDocs.deletedCount > 0) { if (versionsWithoutParentDocs.deletedCount > 0) {
console.log( console.log(
`Removing ${versionsWithoutParentDocs.deletedCount} versions for ${slug} collection - parent documents no longer exist` `Removing ${versionsWithoutParentDocs.deletedCount} versions for ${slug} collection - parent documents no longer exist`,
); )
} }
} }
}) }),
); )
console.log("Done!"); console.log('Done!')
process.exit(0); process.exit(0)
}; }
ensureAtLeastOneVersion(); ensureAtLeastOneVersion()
``` ```
Make sure your environment variables match the script's values above and then run `PAYLOAD_CONFIG_PATH=src/payload.config.ts npx ts-node -T migrateVersions.ts` in your terminal. Make sure that you point the command to your Payload config. Make sure your environment variables match the script's values above and then run `PAYLOAD_CONFIG_PATH=src/payload.config.ts npx ts-node -T migrateVersions.ts` in your terminal. Make sure that you point the command to your Payload config.
@@ -1368,32 +1307,32 @@ Any future slugs after updating will be used as-is.
// Before // Before
const ExampleCollection: CollectionConfig = { const ExampleCollection: CollectionConfig = {
slug: "case-studies", slug: 'case-studies',
labels: { labels: {
// Before Payload used `labels.singular` to generate types/graphQL schema // Before Payload used `labels.singular` to generate types/graphQL schema
singular: "Project", singular: 'Project',
plural: "Projects", plural: 'Projects',
}, },
}; }
// After // After
const ExampleCollection: CollectionConfig = { const ExampleCollection: CollectionConfig = {
// Now Payload uses `slug` to generate types/graphQL schema // Now Payload uses `slug` to generate types/graphQL schema
slug: "case-studies", slug: 'case-studies',
labels: { labels: {
singular: "Project", singular: 'Project',
plural: "Projects", plural: 'Projects',
}, },
// To override the usage of slug in graphQL schema generation // To override the usage of slug in graphQL schema generation
graphQL: { graphQL: {
singularName: "Project", singularName: 'Project',
pluralName: "Projects", pluralName: 'Projects',
}, },
// To override the usage of slug in type file generation // To override the usage of slug in type file generation
typescript: { typescript: {
interface: "Project", interface: 'Project',
}, },
}; }
``` ```
- **Globals:** are affected if you have a `label` defined that differs from your global slug. - **Globals:** are affected if you have a `label` defined that differs from your global slug.
@@ -1403,25 +1342,25 @@ Any future slugs after updating will be used as-is.
// Before // Before
const ExampleGlobal: GlobalConfig = { const ExampleGlobal: GlobalConfig = {
slug: "footer", slug: 'footer',
// Before Payload used `label` to generate types/graphQL schema // Before Payload used `label` to generate types/graphQL schema
label: "Page Footer", label: 'Page Footer',
}; }
// After // After
const ExampleGlobal: GlobalConfig = { const ExampleGlobal: GlobalConfig = {
// Now Payload uses `slug` to generate types/graphQL schema // Now Payload uses `slug` to generate types/graphQL schema
slug: "footer", slug: 'footer',
label: "Page Footer", label: 'Page Footer',
// To override the usage of slug in graphQL schema generation // To override the usage of slug in graphQL schema generation
graphQL: { graphQL: {
name: "PageFooter", name: 'PageFooter',
}, },
// To override the usage of slug in type file generation // To override the usage of slug in type file generation
typescript: { typescript: {
interface: "PageFooter", interface: 'PageFooter',
}, },
}; }
``` ```
- **Block Fields:** are affected if you have a `label` defined that differs from your block slug. - **Block Fields:** are affected if you have a `label` defined that differs from your block slug.
@@ -2694,25 +2633,25 @@ Now, configs will be sanitized **_before_** plugins are executed **_as well as_*
So, where your plugin may have been typed like this before: So, where your plugin may have been typed like this before:
```ts ```ts
import { SanitizedConfig } from "payload/config"; import { SanitizedConfig } from 'payload/config'
const plugin = (config: SanitizedConfig): SanitizedConfig => { const plugin = (config: SanitizedConfig): SanitizedConfig => {
return { return {
...config, ...config,
}; }
}; }
``` ```
It can now be written like this: It can now be written like this:
```ts ```ts
import { Config } from "payload/config"; import { Config } from 'payload/config'
const plugin = (config: Config): Config => { const plugin = (config: Config): Config => {
return { return {
...config, ...config,
}; }
}; }
``` ```
### Features ### Features
@@ -2944,24 +2883,24 @@ For example, if you have a `pages` collection with no existing access control, a
```js ```js
const Page = { const Page = {
slug: "pages", slug: 'pages',
access: { access: {
// No `read` access control was set // No `read` access control was set
}, },
}; }
``` ```
To: To:
```js ```js
const Page = { const Page = {
slug: "pages", slug: 'pages',
access: { access: {
// Now we explicitly allow public read access // Now we explicitly allow public read access
// to this collection's documents // to this collection's documents
read: () => true, read: () => true,
}, },
}; }
``` ```
If none of your collections or globals should be publicly exposed, you don't need to do anything to upgrade. If none of your collections or globals should be publicly exposed, you don't need to do anything to upgrade.
@@ -3522,4 +3461,4 @@ If none of your collections or globals should be publicly exposed, you don't nee
- add blind index for encrypting API Keys ([9a1c1f6](https://github.com/payloadcms/payload/commit/9a1c1f64c0ea0066b679195f50e6cb1ac4bf3552)) - add blind index for encrypting API Keys ([9a1c1f6](https://github.com/payloadcms/payload/commit/9a1c1f64c0ea0066b679195f50e6cb1ac4bf3552))
- add license key to access routej ([2565005](https://github.com/payloadcms/payload/commit/2565005cc099797a6e3b8995e0984c28b7837e82)) - add license key to access routej ([2565005](https://github.com/payloadcms/payload/commit/2565005cc099797a6e3b8995e0984c28b7837e82))
## [0.0.137](https://github.com/payloadcms/payload/commit/5c1e2846a2694a80cc8707703406c2ac1bb6af8a) (2020-11-12) ## [0.0.137](https://github.com/payloadcms/payload/commit/5c1e2846a2694a80cc8707703406c2ac1bb6af8a) (2020-11-12)

View File

@@ -6,9 +6,10 @@ To report an issue, please follow the steps below:
2. Add necessary collections/globals/fields to the `test/_community` directory to recreate the issue you are experiencing 2. Add necessary collections/globals/fields to the `test/_community` directory to recreate the issue you are experiencing
3. Create an issue and add a link to your forked repo 3. Create an issue and add a link to your forked repo
**The goal is to isolate the problem by reducing the number of fields/collections you add to the test/_community folder. This folder is not meant for you to copy your project into, but to recreate the issue you are experiencing with minimal config.** **The goal is to isolate the problem by reducing the number of fields/collections you add to the test/\_community folder. This folder is not meant for you to copy your project into, but to recreate the issue you are experiencing with minimal config.**
## Test directory file tree explanation ## Test directory file tree explanation
```text ```text
. .
├── config.ts ├── config.ts
@@ -25,19 +26,21 @@ To report an issue, please follow the steps below:
The directory split up in this way specifically to reduce friction when creating tests and to add the ability to boot up Payload with that specific config. You should modify the files in `test/_community` to get started. The directory split up in this way specifically to reduce friction when creating tests and to add the ability to boot up Payload with that specific config. You should modify the files in `test/_community` to get started.
## How to start test collection admin UI ## How to start test collection admin UI
To start the admin panel so you can manually recreate your issue, you can run the following command: To start the admin panel so you can manually recreate your issue, you can run the following command:
```bash ```bash
# This command will start up Payload using your config # This command will start up Payload using your config
# NOTE: it will wipe the test database on restart # NOTE: it will wipe the test database on restart
pnpm dev _community pnpm dev _community
``` ```
## Testing is optional but encouraged ## Testing is optional but encouraged
An issue does not need to have failing tests — reproduction steps with your forked repo are enough at this point. Some people like to dive deeper and we want to give you the guidance/tools to do so. Read more below. An issue does not need to have failing tests — reproduction steps with your forked repo are enough at this point. Some people like to dive deeper and we want to give you the guidance/tools to do so. Read more below.
### How to run integration tests (Payload API tests) ### How to run integration tests (Payload API tests)
There are a couple ways to do this: There are a couple ways to do this:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points. - **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
@@ -51,7 +54,9 @@ There are a couple ways to do this:
``` ```
### How to run E2E tests (Admin Panel UI tests) ### How to run E2E tests (Admin Panel UI tests)
The easiest way to run E2E tests is to install The easiest way to run E2E tests is to install
- [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) - [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
- [Playwright Runner](https://marketplace.visualstudio.com/items?itemName=ortoni.ortoni) - [Playwright Runner](https://marketplace.visualstudio.com/items?itemName=ortoni.ortoni)
@@ -59,6 +64,6 @@ Once they are installed you can open the `testing` tab in vscode sidebar and dri
<img src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/github/e2e-debug.png" /> <img src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/github/e2e-debug.png" />
#### Notes #### Notes
- It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart. The default credentials are `dev@payloadcms.com` as email and `test` as password. - It is recommended to add the test credentials (located in `test/credentials.ts`) to your autofill for `localhost:3000/admin` as this will be required on every nodemon restart. The default credentials are `dev@payloadcms.com` as email and `test` as password.

View File

@@ -39,6 +39,7 @@
</ul> </ul>
## ☁️ Deploy instantly with Payload Cloud. ## ☁️ Deploy instantly with Payload Cloud.
Create a cloud account, connect your GitHub, and [deploy in minutes](https://payloadcms.com/new). Create a cloud account, connect your GitHub, and [deploy in minutes](https://payloadcms.com/new).
## 🚀 Get started by self-hosting completely free, forever. ## 🚀 Get started by self-hosting completely free, forever.
@@ -52,7 +53,9 @@ npx create-payload-app
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch). Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
## 🖱️ One-click templates ## 🖱️ One-click templates
### 🛒 [E-Commerce](https://github.com/payloadcms/payload/tree/master/templates/ecommerce) ### 🛒 [E-Commerce](https://github.com/payloadcms/payload/tree/master/templates/ecommerce)
Eliminate the need to combine Shopify and a CMS, and instead do it all with Payload + Stripe. Best of all, you can extend it as much as you need. Eliminate the need to combine Shopify and a CMS, and instead do it all with Payload + Stripe. Best of all, you can extend it as much as you need.
[All Official Templates](https://github.com/orgs/payloadcms/repositories?q=topic%3Apayload-template)&nbsp;·&nbsp;[Community Templates](https://github.com/topics/payload-template) [All Official Templates](https://github.com/orgs/payloadcms/repositories?q=topic%3Apayload-template)&nbsp;·&nbsp;[Community Templates](https://github.com/topics/payload-template)

View File

@@ -10,23 +10,24 @@ You can define Collection-level Access Control within each Collection's `access`
## Available Controls ## Available Controls
| Function | Allows/Denies Access | | Function | Allows/Denies Access |
| ------------------------ | -------------------- | | ----------------------- | -------------------------------------------- |
| **[`create`](#create)** | Used in the `create` operation | | **[`create`](#create)** | Used in the `create` operation |
| **[`read`](#read)** | Used in the `find` and `findByID` operations | | **[`read`](#read)** | Used in the `find` and `findByID` operations |
| **[`update`](#update)** | Used in the `update` operation | | **[`update`](#update)** | Used in the `update` operation |
| **[`delete`](#delete)** | Used in the `delete` operation | | **[`delete`](#delete)** | Used in the `delete` operation |
#### Auth-enabled Controls #### Auth-enabled Controls
If a Collection supports [`Authentication`](/docs/authentication/overview), the following Access Controls become available: If a Collection supports [`Authentication`](/docs/authentication/overview), the following Access Controls become available:
| Function | Allows/Denies Access | | Function | Allows/Denies Access |
| ----------------------- | -------------------- | | ----------------------- | -------------------------------------------------------------- |
| **[`admin`](#admin)** | Used to restrict access to the Payload Admin panel | | **[`admin`](#admin)** | Used to restrict access to the Payload Admin panel |
| **[`unlock`](#unlock)** | Used to restrict which users can access the `unlock` operation | | **[`unlock`](#unlock)** | Used to restrict which users can access the `unlock` operation |
**Example Collection config:** **Example Collection config:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types';
@@ -50,10 +51,10 @@ Returns a boolean which allows/denies access to the `create` request.
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ---------- | ----------- | | ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The data passed to create the document with. | | **`data`** | The data passed to create the document with. |
**Example:** **Example:**
@@ -77,20 +78,20 @@ Read access functions can return a boolean result or optionally return a [query
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| --------- | ----------- | | --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of document requested, if within `findByID` | | **`id`** | `id` of document requested, if within `findByID` |
**Example:** **Example:**
```ts ```ts
import { Access } from 'payload/config'; import { Access } from 'payload/config'
const canReadPage: Access = ({ req: { user } }) => { const canReadPage: Access = ({ req: { user } }) => {
// allow authenticated users // allow authenticated users
if (user) { if (user) {
return true; return true
} }
// using a query constraint, guest users can access when a field named 'isPublic' is set to true // using a query constraint, guest users can access when a field named 'isPublic' is set to true
return { return {
@@ -99,7 +100,7 @@ const canReadPage: Access = ({ req: { user } }) => {
equals: true, equals: true,
}, },
} }
}; }
``` ```
### Update ### Update
@@ -108,25 +109,25 @@ Update access functions can return a boolean result or optionally return a [quer
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ---------- | ----------- | | ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of document requested to update | | **`id`** | `id` of document requested to update |
| **`data`** | The data passed to update the document with | | **`data`** | The data passed to update the document with |
**Example:** **Example:**
```ts ```ts
import { Access } from 'payload/config'; import { Access } from 'payload/config'
const canUpdateUser: Access = ({ req: { user }, id }) => { const canUpdateUser: Access = ({ req: { user }, id }) => {
// allow users with a role of 'admin' // allow users with a role of 'admin'
if (user.roles && user.roles.some(role => role === 'admin')) { if (user.roles && user.roles.some((role) => role === 'admin')) {
return true; return true
} }
// allow any other users to update only oneself // allow any other users to update only oneself
return user.id === id; return user.id === id
}; }
``` ```
### Delete ### Delete
@@ -135,10 +136,10 @@ Similarly to the Update function, returns a boolean or a [query constraint](/doc
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| --------- | ----------- | | --------- | --------------------------------------------------------------------------------------------------- |
| **`req`** | The Express `request` object with additional `user` property, which is the currently logged in user | | **`req`** | The Express `request` object with additional `user` property, which is the currently logged in user |
| **`id`** | `id` of document requested to delete | | **`id`** | `id` of document requested to delete |
**Example:** **Example:**
@@ -148,7 +149,7 @@ import { Access } from 'payload/config'
const canDeleteCustomer: Access = async ({ req, id }) => { const canDeleteCustomer: Access = async ({ req, id }) => {
if (!id) { if (!id) {
// allow the admin UI to show controls to delete since it is indeterminate without the id // allow the admin UI to show controls to delete since it is indeterminate without the id
return true; return true
} }
// query another collection using the id // query another collection using the id
const result = await req.payload.find({ const result = await req.payload.find({
@@ -158,10 +159,10 @@ const canDeleteCustomer: Access = async ({ req, id }) => {
where: { where: {
customer: { equals: id }, customer: { equals: id },
}, },
}); })
return result.totalDocs === 0; return result.totalDocs === 0
}; }
``` ```
### Admin ### Admin
@@ -170,8 +171,8 @@ If the Collection is [used to access the Payload Admin panel](/docs/admin/overvi
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| --------- | ----------- | | --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
### Unlock ### Unlock
@@ -180,6 +181,6 @@ Determines which users can [unlock](/docs/authentication/operations#unlock) othe
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| --------- | ----------- | | --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |

View File

@@ -10,13 +10,14 @@ Field Access Control is specified with functions inside a field's config. All fi
## Available Controls ## Available Controls
| Function | Purpose | | Function | Purpose |
| ------------------------ | ------- | | ----------------------- | -------------------------------------------------------------------------------- |
| **[`create`](#create)** | Allows or denies the ability to set a field's value when creating a new document | | **[`create`](#create)** | Allows or denies the ability to set a field's value when creating a new document |
| **[`read`](#read)** | Allows or denies the ability to read a field's value | | **[`read`](#read)** | Allows or denies the ability to read a field's value |
| **[`update`](#update)** | Allows or denies the ability to update a field's value | | **[`update`](#update)** | Allows or denies the ability to update a field's value |
**Example Collection config:** **Example Collection config:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types';
@@ -44,11 +45,11 @@ Returns a boolean which allows or denies the ability to set a field's value when
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ----------------- | ----------- | | ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The full data passed to create the document. | | **`data`** | The full data passed to create the document. |
| **`siblingData`** | Immediately adjacent field data passed to create the document. | | **`siblingData`** | Immediately adjacent field data passed to create the document. |
### Read ### Read
@@ -56,12 +57,12 @@ Returns a boolean which allows or denies the ability to read a field's value. If
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ----------------- | ----------- | | ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of the document being read | | **`id`** | `id` of the document being read |
| **`doc`** | The full document data. | | **`doc`** | The full document data. |
| **`siblingData`** | Immediately adjacent field data of the document being read. | | **`siblingData`** | Immediately adjacent field data of the document being read. |
### Update ### Update
@@ -71,10 +72,10 @@ If `false` is returned and you attempt to update the field's value, the operatio
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ----------------- | ----------- | | ----------------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`id`** | `id` of the document being updated | | **`id`** | `id` of the document being updated |
| **`data`** | The full data passed to update the document. | | **`data`** | The full data passed to update the document. |
| **`siblingData`** | Immediately adjacent field data passed to update the document with. | | **`siblingData`** | Immediately adjacent field data passed to update the document with. |
| **`doc`** | The full document data, before the update is applied. | | **`doc`** | The full document data, before the update is applied. |

View File

@@ -8,30 +8,35 @@ keywords: globals, access control, permissions, documentation, Content Managemen
You can define Global-level Access Control within each Global's `access` property. All Access Control functions accept one `args` argument. You can define Global-level Access Control within each Global's `access` property. All Access Control functions accept one `args` argument.
**Available argument properties: \*\*Available argument properties:
## Available Controls ## Available Controls
| Function | Allows/Denies Access | | Function | Allows/Denies Access |
| ------------------------ | -------------------- | | ----------------------- | -------------------------------------- |
| **[`read`](#read)** | Used in the `findOne` Global operation | | **[`read`](#read)** | Used in the `findOne` Global operation |
| **[`update`](#update)** | Used in the `update` Global operation | | **[`update`](#update)** | Used in the `update` Global operation |
**Example Global config:** **Example Global config:**
```ts ```ts
import { GlobalConfig } from 'payload/types'; import { GlobalConfig } from 'payload/types'
const Header: GlobalConfig = { const Header: GlobalConfig = {
slug: "header", slug: 'header',
// highlight-start // highlight-start
access: { access: {
read: ({ req: { user } }) => { /* */ }, read: ({ req: { user } }) => {
update: ({ req: { user } }) => { /* */ }, /* */
},
update: ({ req: { user } }) => {
/* */
},
}, },
// highlight-end // highlight-end
}; }
export default Header; export default Header
``` ```
### Read ### Read
@@ -40,8 +45,8 @@ Returns a boolean result or optionally a [query constraint](/docs/queries/overvi
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| --------- | ----------- | | --------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
### Update ### Update
@@ -50,7 +55,7 @@ Returns a boolean result or optionally a [query constraint](/docs/queries/overvi
**Available argument properties:** **Available argument properties:**
| Option | Description | | Option | Description |
| ---------- | ----------- | | ---------- | -------------------------------------------------------------------------- |
| **`req`** | The Express `request` object containing the currently authenticated `user` | | **`req`** | The Express `request` object containing the currently authenticated `user` |
| **`data`** | The data passed to update the global with. | | **`data`** | The data passed to update the global with. |

View File

@@ -8,10 +8,7 @@ keywords: overview, access control, permissions, documentation, Content Manageme
Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a `boolean` or a [`query`](/docs/queries/overview) constraint to restrict which documents users can interact with. Access control within Payload is extremely powerful while remaining easy and intuitive to manage. Declaring who should have access to what documents is no more complex than writing a simple JavaScript function that either returns a `boolean` or a [`query`](/docs/queries/overview) constraint to restrict which documents users can interact with.
<YouTube <YouTube id="DoPLyXG26Dg" title="Overview of Payload Access Control" />
id="DoPLyXG26Dg"
title="Overview of Payload Access Control"
/>
**Example use cases:** **Example use cases:**
@@ -32,13 +29,18 @@ Access control within Payload is extremely powerful while remaining easy and int
const defaultPayloadAccess = ({ req: { user } }) => { const defaultPayloadAccess = ({ req: { user } }) => {
// Return `true` if a user is found // Return `true` if a user is found
// and `false` if it is undefined or null // and `false` if it is undefined or null
return Boolean(user); return Boolean(user)
} }
``` ```
<Banner type="success"> <Banner type="success">
<strong>Note:</strong><br/> <strong>Note:</strong>
In the Local API, all Access Control functions are skipped by default, allowing your server to do whatever it needs. But, you can opt back in by setting the option <strong>overrideAccess</strong> to <strong>false</strong>. <br />
In the Local API, all Access Control functions are skipped by default, allowing your server to do
whatever it needs. But, you can opt back in by setting the option <strong>
overrideAccess
</strong>{' '}
to <strong>false</strong>.
</Banner> </Banner>
### Access Control Types ### Access Control Types
@@ -49,12 +51,13 @@ You can manage access within Payload on three different levels:
- [Fields](/docs/access-control/fields) - [Fields](/docs/access-control/fields)
- [Globals](/docs/access-control/globals) - [Globals](/docs/access-control/globals)
### When Access Control is Executed ### When Access Control is Executed
<Banner type="success"> <Banner type="success">
<strong>Note:</strong><br/> <strong>Note:</strong>
Access control functions are utilized in two places. It's important to understand how and when your access control is executed. <br />
Access control functions are utilized in two places. It's important to understand how and when
your access control is executed.
</Banner> </Banner>
#### As you execute operations #### As you execute operations
@@ -70,8 +73,11 @@ To accomplish this, Payload ships with an `Access` operation, which is executed
### Argument Availability ### Argument Availability
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong><br/> <strong>Important:</strong>
When your access control functions are executed via the <strong>access</strong> operation, the <strong>id</strong> and <strong>data</strong> arguments will be <strong>undefined</strong>, because Payload is executing your functions without referencing a specific document. <br />
When your access control functions are executed via the <strong>access</strong> operation, the{' '}
<strong>id</strong> and <strong>data</strong> arguments will be <strong>undefined</strong>,
because Payload is executing your functions without referencing a specific document.
</Banner> </Banner>
If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your access control is being executed via the `access` operation, to determine solely what the user can do within the Admin UI. If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your access control is being executed via the `access` operation, to determine solely what the user can do within the Admin UI.

View File

@@ -11,8 +11,10 @@ While designing the Payload Admin panel, we determined it should be as minimal a
To swap in your own React component, first, consult the list of available component overrides below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly. To swap in your own React component, first, consult the list of available component overrides below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br /> <strong>Tip:</strong>
Custom components will automatically be provided with all props that the default component would accept. <br />
Custom components will automatically be provided with all props that the default component would
accept.
</Banner> </Banner>
### Base Component Overrides ### Base Component Overrides
@@ -41,7 +43,7 @@ You can override a set of admin panel-wide components by providing a component t
`payload.config.js` `payload.config.js`
```ts ```ts
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
import { import {
MyCustomNav, MyCustomNav,
MyCustomLogo, MyCustomLogo,
@@ -49,7 +51,7 @@ import {
MyCustomAccount, MyCustomAccount,
MyCustomDashboard, MyCustomDashboard,
MyProvider, MyProvider,
} from "./customComponents"; } from './customComponents'
export default buildConfig({ export default buildConfig({
admin: { admin: {
@@ -66,7 +68,7 @@ export default buildConfig({
providers: [MyProvider], providers: [MyProvider],
}, },
}, },
}); })
``` ```
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._ _For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._
@@ -93,20 +95,17 @@ You can override components on a Collection-by-Collection basis via each Collect
```tsx ```tsx
// Custom Buttons // Custom Buttons
import * as React from "react"; import * as React from 'react'
import { import {
CustomSaveButtonProps, CustomSaveButtonProps,
CustomSaveDraftButtonProps, CustomSaveDraftButtonProps,
CustomPublishButtonProps, CustomPublishButtonProps,
CustomPreviewButtonProps, CustomPreviewButtonProps,
} from "payload/types"; } from 'payload/types'
export const CustomSaveButton: CustomSaveButtonProps = ({ export const CustomSaveButton: CustomSaveButtonProps = ({ DefaultButton, label }) => {
DefaultButton, return <DefaultButton label={label} />
label, }
}) => {
return <DefaultButton label={label} />;
};
export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({ export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({
DefaultButton, DefaultButton,
@@ -114,10 +113,8 @@ export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({
label, label,
saveDraft, saveDraft,
}) => { }) => {
return ( return <DefaultButton label={label} disabled={disabled} saveDraft={saveDraft} />
<DefaultButton label={label} disabled={disabled} saveDraft={saveDraft} /> }
);
};
export const CustomPublishButton: CustomPublishButtonProps = ({ export const CustomPublishButton: CustomPublishButtonProps = ({
DefaultButton, DefaultButton,
@@ -125,8 +122,8 @@ export const CustomPublishButton: CustomPublishButtonProps = ({
label, label,
publish, publish,
}) => { }) => {
return <DefaultButton label={label} disabled={disabled} publish={publish} />; return <DefaultButton label={label} disabled={disabled} publish={publish} />
}; }
export const CustomPreviewButton: CustomPreviewButtonProps = ({ export const CustomPreviewButton: CustomPreviewButtonProps = ({
DefaultButton, DefaultButton,
@@ -134,8 +131,8 @@ export const CustomPreviewButton: CustomPreviewButtonProps = ({
label, label,
preview, preview,
}) => { }) => {
return <DefaultButton label={label} disabled={disabled} preview={preview} />; return <DefaultButton label={label} disabled={disabled} preview={preview} />
}; }
``` ```
##### Custom Collection List View Example ##### Custom Collection List View Example
@@ -162,17 +159,17 @@ export const MyCollection: CollectionConfig = {
MyListComponent.tsx MyListComponent.tsx
```tsx ```tsx
import React from "react"; import React from 'react'
import { List, type Props } from "payload/components/views/List"; // Payload's default List view component and its props import { List, type Props } from 'payload/components/views/List' // Payload's default List view component and its props
export const MyListComponent: React.FC<Props> = (props) => ( export const MyListComponent: React.FC<Props> = (props) => (
<div> <div>
<p> <p>
Some text before the default list view component. If you just want to do Some text before the default list view component. If you just want to do that, you can also
that, you can also use the admin.components.list.BeforeList hook use the admin.components.list.BeforeList hook
</p> </p>
<List {...props} /> <List {...props} />
</div> </div>
); )
``` ```
### Globals ### Globals
@@ -194,10 +191,9 @@ All Payload fields support the ability to swap in your own React components. So,
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br /> <br />
Don't see a built-in field type that you need? Build it! Using a combination Don't see a built-in field type that you need? Build it! Using a combination of custom validation
of custom validation and custom components, you can override the entirety of and custom components, you can override the entirety of how a component functions within the admin
how a component functions within the admin panel and effectively create your panel and effectively create your own field type.
own field type.
</Banner> </Banner>
**Fields support the following custom components:** **Fields support the following custom components:**
@@ -223,15 +219,15 @@ These are the props that will be passed to your custom Cell to use in your own c
#### Example #### Example
```tsx ```tsx
import React from "react"; import React from 'react'
import "./index.scss"; import './index.scss'
const baseClass = "custom-cell"; const baseClass = 'custom-cell'
const CustomCell: React.FC<Props> = (props) => { const CustomCell: React.FC<Props> = (props) => {
const { field, colIndex, collection, cellData, rowData } = props; const { field, colIndex, collection, cellData, rowData } = props
return <span className={baseClass}>{cellData}</span>; return <span className={baseClass}>{cellData}</span>
}; }
``` ```
## Field Component ## Field Component
@@ -243,25 +239,22 @@ When writing your own custom components you can make use of a number of hooks to
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows: When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
```tsx ```tsx
import { useField } from "payload/components/forms"; import { useField } from 'payload/components/forms'
type Props = { path: string }; type Props = { path: string }
const CustomTextField: React.FC<Props> = ({ path }) => { const CustomTextField: React.FC<Props> = ({ path }) => {
// highlight-start // highlight-start
const { value, setValue } = useField<Props>({ path }); const { value, setValue } = useField<Props>({ path })
// highlight-end // highlight-end
return ( return <input onChange={(e) => setValue(e.target.value)} value={value.path} />
<input onChange={(e) => setValue(e.target.value)} value={value.path} /> }
);
};
``` ```
<Banner type="success"> <Banner type="success">
For more information regarding the hooks that are available to you while you For more information regarding the hooks that are available to you while you build custom
build custom components, including the <strong>useField</strong> hook, [click components, including the <strong>useField</strong> hook, [click here](/docs/admin/hooks).
here](/docs/admin/hooks).
</Banner> </Banner>
## Custom routes ## Custom routes
@@ -292,9 +285,8 @@ Your custom route components will be given all the props that a React Router `<R
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
It's up to you to secure your custom routes. If your route requires a user to It's up to you to secure your custom routes. If your route requires a user to be logged in or to
be logged in or to have certain access rights, you should handle that within have certain access rights, you should handle that within your route component yourself.
your route component yourself.
</Banner> </Banner>
#### Example #### Example
@@ -311,8 +303,8 @@ To see how to pass in your custom views to create custom routes of your own, tak
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more. As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more.
<Banner type="warning"> <Banner type="warning">
<strong>Reminder:</strong> Don't forget to pass the **children** prop through <strong>Reminder:</strong> Don't forget to pass the **children** prop through the provider
the provider component for the admin UI to show component for the admin UI to show
</Banner> </Banner>
### Styling Custom Components ### Styling Custom Components
@@ -332,21 +324,21 @@ When developing custom components you can support multiple languages to be consi
For example: For example:
```tsx ```tsx
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next'
const CustomComponent: React.FC = () => { const CustomComponent: React.FC = () => {
// highlight-start // highlight-start
const { t, i18n } = useTranslation("namespace1"); const { t, i18n } = useTranslation('namespace1')
// highlight-end // highlight-end
return ( return (
<ul> <ul>
<li>{t("key", { variable: "value" })}</li> <li>{t('key', { variable: 'value' })}</li>
<li>{t("namespace2:key", { variable: "value" })}</li> <li>{t('namespace2:key', { variable: 'value' })}</li>
<li>{i18n.language}</li> <li>{i18n.language}</li>
</ul> </ul>
); )
}; }
``` ```
### Getting the current locale ### Getting the current locale
@@ -354,18 +346,18 @@ const CustomComponent: React.FC = () => {
In any custom component you can get the selected locale with `useLocale` hook. `useLocale` returns the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example: In any custom component you can get the selected locale with `useLocale` hook. `useLocale` returns the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
```tsx ```tsx
import { useLocale } from "payload/components/utilities"; import { useLocale } from 'payload/components/utilities'
const Greeting: React.FC = () => { const Greeting: React.FC = () => {
// highlight-start // highlight-start
const locale = useLocale(); const locale = useLocale()
// highlight-end // highlight-end
const trans = { const trans = {
en: "Hello", en: 'Hello',
es: "Hola", es: 'Hola',
}; }
return <span> {trans[locale.code]} </span> return <span> {trans[locale.code]} </span>
}; }
``` ```

View File

@@ -13,15 +13,16 @@ You can add your own CSS by providing your base Payload config with a path to yo
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file. To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
**Example in payload.config.js:** **Example in payload.config.js:**
```ts ```ts
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
import path from 'path'; import path from 'path'
const config = buildConfig({ const config = buildConfig({
admin: { admin: {
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'), css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
}, },
}); })
``` ```
### Overriding built-in styles ### Overriding built-in styles
@@ -43,7 +44,8 @@ You can find the built-in Payload CSS variables within [`./src/admin/scss/app.sc
#### Dark mode #### Dark mode
<Banner type="warning"> <Banner type="warning">
If you're overriding colors or theme elevations, make sure to consider how your changes will affect dark mode. If you're overriding colors or theme elevations, make sure to consider how your changes will
affect dark mode.
</Banner> </Banner>
By default, Payload automatically overrides all `--theme-elevation`s and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc. By default, Payload automatically overrides all `--theme-elevation`s and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc.

View File

@@ -24,7 +24,7 @@ const CustomTextField: React.FC<Props> = ({ path }) => {
const { value, setValue } = useField<string>({ path }) const { value, setValue } = useField<string>({ path })
// highlight-end // highlight-end
return <input onChange={e => setValue(e.target.value)} value={value.path} /> return <input onChange={(e) => setValue(e.target.value)} value={value.path} />
} }
``` ```
@@ -57,7 +57,8 @@ const {
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form. There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
<Banner type="success"> <Banner type="success">
<strong>This hook is great for retrieving only certain fields from form state</strong> because it ensures that it will only cause a rerender when the items that you ask for change. <strong>This hook is great for retrieving only certain fields from form state</strong> because it
ensures that it will only cause a rerender when the items that you ask for change.
</Banner> </Banner>
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes. Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
@@ -65,21 +66,19 @@ Thanks to the awesome package [`use-context-selector`](https://github.com/dai-sh
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`. You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
```tsx ```tsx
import { useFormFields } from 'payload/components/forms'; import { useFormFields } from 'payload/components/forms'
const MyComponent: React.FC = () => { const MyComponent: React.FC = () => {
// Get only the `amount` field state, and only cause a rerender when that field changes // Get only the `amount` field state, and only cause a rerender when that field changes
const amount = useFormFields(([fields, dispatch]) => fields.amount); const amount = useFormFields(([fields, dispatch]) => fields.amount)
// Do the same thing as above, but to the `feePercentage` field // Do the same thing as above, but to the `feePercentage` field
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage); const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage)
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') { if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
return ( return <span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
<span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
);
} }
}; }
``` ```
### useAllFormFields ### useAllFormFields
@@ -117,7 +116,7 @@ If you are building a custom component, then you should use `setValue` which is
You can send the following actions to the `dispatchFields` function. You can send the following actions to the `dispatchFields` function.
| Action | Description | | Action | Description |
|------------------------|----------------------------------------------------------------------------| | ---------------------- | -------------------------------------------------------------------------- |
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) | | **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) | | **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) | | **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
@@ -134,8 +133,12 @@ To see types for each action supported within the `dispatchFields` hook, check o
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_. The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
<Banner type="warning"> <Banner type="warning">
<strong>Warning:</strong><br/> <strong>Warning:</strong>
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields` property will be out of date. You should only leverage this hook if you need to perform actions against the form in response to your users' actions. Do not rely on its returned "fields" as being up-to-date. They will be removed from this hook's response in an upcoming version. <br />
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields`
property will be out of date. You should only leverage this hook if you need to perform actions
against the form in response to your users' actions. Do not rely on its returned "fields" as being
up-to-date. They will be removed from this hook's response in an upcoming version.
</Banner> </Banner>
The `useForm` hook returns an object with the following properties: | The `useForm` hook returns an object with the following properties: |
@@ -358,10 +361,14 @@ The `useForm` hook returns an object with the following properties: |
]} ]}
/> />
<br /> {' '}
<pre> <br />
{`import { useForm } from "payload/components/forms";
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => { export const CustomArrayManager = () => {
const { addFieldRow } = useForm() const { addFieldRow } = useForm()
@@ -385,7 +392,7 @@ export const CustomArrayManager = () => {
</button> </button>
) )
}`} }`}
</pre> </pre>
<p>An example config to go along with the custom component</p> <p>An example config to go along with the custom component</p>
<pre> <pre>
@@ -456,10 +463,14 @@ export const CustomArrayManager = () => {
]} ]}
/> />
<br /> {' '}
<pre> <br />
{`import { useForm } from "payload/components/forms";
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => { export const CustomArrayManager = () => {
const { removeFieldRow } = useForm() const { removeFieldRow } = useForm()
@@ -478,7 +489,7 @@ export const CustomArrayManager = () => {
</button> </button>
) )
}`} }`}
</pre> </pre>
<p>An example config to go along with the custom component</p> <p>An example config to go along with the custom component</p>
<pre> <pre>
@@ -557,10 +568,14 @@ export const CustomArrayManager = () => {
]} ]}
/> />
<br /> {' '}
<pre> <br />
{`import { useForm } from "payload/components/forms";
{' '}
<pre>
{`import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => { export const CustomArrayManager = () => {
const { replaceFieldRow } = useForm() const { replaceFieldRow } = useForm()
@@ -584,7 +599,7 @@ export const CustomArrayManager = () => {
</button> </button>
) )
}`} }`}
</pre> </pre>
<p>An example config to go along with the custom component</p> <p>An example config to go along with the custom component</p>
<pre> <pre>
@@ -624,40 +639,40 @@ export const CustomArrayManager = () => {
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following: The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
| Property | Description | | Property | Description |
|---------------------------|--------------------------------------------------------------------------------------------------------------------| | |---------------------------|--------------------------------------------------------------------------------------------------------------------| |
| **`collection`** | If the doc is a collection, its collection config will be returned | | **`collection`** | If the doc is a collection, its collection config will be returned |
| **`global`** | If the doc is a global, its global config will be returned | | **`global`** | If the doc is a global, its global config will be returned |
| **`id`** | If the doc is a collection, its ID will be returned | | **`id`** | If the doc is a collection, its ID will be returned |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences | | **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
| **`versions`** | Versions of the current doc | | **`versions`** | Versions of the current doc |
| **`unpublishedVersions`** | Unpublished versions of the current doc | | **`unpublishedVersions`** | Unpublished versions of the current doc |
| **`publishedDoc`** | The currently published version of the doc being edited | | **`publishedDoc`** | The currently published version of the doc being edited |
| **`getVersions`** | Method to trigger the retrieval of document versions | | **`getVersions`** | Method to trigger the retrieval of document versions |
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create) | | **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create) |
| **`getDocPermissions`** | Method to trigger the retrieval of document level permissions | | **`getDocPermissions`** | Method to trigger the retrieval of document level permissions |
**Example:** **Example:**
```tsx ```tsx
import { useDocumentInfo } from 'payload/components/utilities'; import { useDocumentInfo } from 'payload/components/utilities'
const LinkFromCategoryToPosts: React.FC = () => { const LinkFromCategoryToPosts: React.FC = () => {
// highlight-start // highlight-start
const { id } = useDocumentInfo(); const { id } = useDocumentInfo()
// highlight-end // highlight-end
// id will be undefined on the create form // id will be undefined on the create form
if (!id) { if (!id) {
return null; return null
} }
return ( return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`} > <a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}>
View posts View posts
</a> </a>
) )
}; }
``` ```
### useLocale ### useLocale
@@ -665,22 +680,20 @@ const LinkFromCategoryToPosts: React.FC = () => {
In any custom component you can get the selected locale object with the `useLocale` hook. `useLocale`gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example: In any custom component you can get the selected locale object with the `useLocale` hook. `useLocale`gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
```tsx ```tsx
import { useLocale } from 'payload/components/utilities'; import { useLocale } from 'payload/components/utilities'
const Greeting: React.FC = () => { const Greeting: React.FC = () => {
// highlight-start // highlight-start
const locale = useLocale(); const locale = useLocale()
// highlight-end // highlight-end
const trans = { const trans = {
en: 'Hello', en: 'Hello',
es: 'Hola', es: 'Hola',
}; }
return ( return <span> {trans[locale.code]} </span>
<span> { trans[locale.code] } </span> }
);
};
``` ```
### useAuth ### useAuth
@@ -688,7 +701,7 @@ const Greeting: React.FC = () => {
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties: Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
| Property | Description | | Property | Description |
|--------------------------|-----------------------------------------------------------------------------------------| | ------------------------ | --------------------------------------------------------------------------------------- |
| **`user`** | The currently logged in user | | **`user`** | The currently logged in user |
| **`logOut`** | A method to log out the currently logged in user | | **`logOut`** | A method to log out the currently logged in user |
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token | | **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
@@ -698,18 +711,16 @@ Useful to retrieve info about the currently logged in user as well as methods fo
| **`permissions`** | The permissions of the current user | | **`permissions`** | The permissions of the current user |
```tsx ```tsx
import { useAuth } from 'payload/components/utilities'; import { useAuth } from 'payload/components/utilities'
import { User } from '../payload-types.ts'; import { User } from '../payload-types.ts'
const Greeting: React.FC = () => { const Greeting: React.FC = () => {
// highlight-start // highlight-start
const { user } = useAuth<User>(); const { user } = useAuth<User>()
// highlight-end // highlight-end
return ( return <span>Hi, {user.email}!</span>
<span>Hi, {user.email}!</span> }
);
};
``` ```
### useConfig ### useConfig
@@ -717,17 +728,15 @@ const Greeting: React.FC = () => {
Used to easily fetch the full Payload config. Used to easily fetch the full Payload config.
```tsx ```tsx
import { useConfig } from 'payload/components/utilities'; import { useConfig } from 'payload/components/utilities'
const MyComponent: React.FC = () => { const MyComponent: React.FC = () => {
// highlight-start // highlight-start
const config = useConfig(); const config = useConfig()
// highlight-end // highlight-end
return ( return <span>{config.serverURL}</span>
<span>{config.serverURL}</span> }
);
};
``` ```
### useEditDepth ### useEditDepth
@@ -735,16 +744,14 @@ const MyComponent: React.FC = () => {
Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases. Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases.
```tsx ```tsx
import { useEditDepth } from 'payload/components/utilities'; import { useEditDepth } from 'payload/components/utilities'
const MyComponent: React.FC = () => { const MyComponent: React.FC = () => {
// highlight-start // highlight-start
const editDepth = useEditDepth(); const editDepth = useEditDepth()
// highlight-end // highlight-end
return ( return <span>My component is {editDepth} levels deep</span>
<span>My component is {editDepth} levels deep</span>
)
} }
``` ```

View File

@@ -11,9 +11,9 @@ Payload dynamically generates a beautiful, fully functional React admin panel to
The Payload Admin panel is built with Webpack, code-split, highly performant (even with 100+ fields), and written fully in TypeScript. The Payload Admin panel is built with Webpack, code-split, highly performant (even with 100+ fields), and written fully in TypeScript.
<Banner type="success"> <Banner type="success">
The Admin panel is meant to be simple enough to give you a starting point but The Admin panel is meant to be simple enough to give you a starting point but not bring too much
not bring too much complexity, so that you can easily customize it to suit the complexity, so that you can easily customize it to suit the needs of your application and your
needs of your application and your editors. editors.
</Banner> </Banner>
![Payload's Admin panel built in React](https://payloadcms.com/images/docs/admin.jpg) ![Payload's Admin panel built in React](https://payloadcms.com/images/docs/admin.jpg)
@@ -25,7 +25,7 @@ _Screenshot of the Admin panel while editing a document from an example `AllFiel
All options for the Admin panel are defined in your base Payload config file. All options for the Admin panel are defined in your base Payload config file.
| Option | Description | | Option | Description |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) | | `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
| `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. | | `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. | | `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
@@ -55,13 +55,13 @@ To specify which Collection to use to log in to the Admin panel, pass the `admin
`payload.config.js`: `payload.config.js`:
```ts ```ts
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
const config = buildConfig({ const config = buildConfig({
admin: { admin: {
user: "admins", // highlight-line user: 'admins', // highlight-line
}, },
}); })
``` ```
By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version. By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version.

View File

@@ -15,8 +15,10 @@ Out of the box, Payload handles the persistence of your users' preferences in a
1. The "collapsed" state of blocks, on a document level, as users edit or interact with documents 1. The "collapsed" state of blocks, on a document level, as users edit or interact with documents
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong><br/> <strong>Important:</strong>
All preferences are stored on an individual user basis. Payload automatically recognizes the user that is reading or setting a preference via all provided authentication methods. <br />
All preferences are stored on an individual user basis. Payload automatically recognizes the user
that is reading or setting a preference via all provided authentication methods.
</Banner> </Banner>
### Use cases ### Use cases
@@ -33,7 +35,7 @@ This API is used significantly for internal operations of the Admin panel, as me
Payload automatically creates an internally used `payload-preferences` collection that stores user preferences. Each document in the `payload-preferences` collection contains the following shape: Payload automatically creates an internally used `payload-preferences` collection that stores user preferences. Each document in the `payload-preferences` collection contains the following shape:
| Key | Value | | Key | Value |
|-------------------|-------------------------------------------------------------------| | ----------------- | ----------------------------------------------------------------- |
| `id` | A unique ID for each preference stored. | | `id` | A unique ID for each preference stored. |
| `key` | A unique `key` that corresponds to the preference. | | `key` | A unique `key` that corresponds to the preference. |
| `user.value` | The ID of the `user` that is storing its preference. | | `user.value` | The ID of the `user` that is storing its preference. |

View File

@@ -11,20 +11,21 @@ Payload uses Webpack 5 to build the Admin panel. It comes with support for many
To extend the Webpack config, add the `webpack` key to your base Payload config, and provide a function that accepts the default Webpack config as its only argument: To extend the Webpack config, add the `webpack` key to your base Payload config, and provide a function that accepts the default Webpack config as its only argument:
`payload.config.ts` `payload.config.ts`
```ts ```ts
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
admin: { admin: {
// highlight-start // highlight-start
webpack: (config) => { webpack: (config) => {
// Do something with the config here // Do something with the config here
return config; return config
} },
// highlight-end // highlight-end
} },
}); })
``` ```
### Aliasing server-only modules ### Aliasing server-only modules
@@ -43,73 +44,84 @@ Examples of **non** browser-friendly packages:
You may rely on server-only packages such as the above to perform logic in access control functions, hooks, and other contexts (which is great!) but when you boot up your Payload app and try to view it in the browser, you'll likely run into missing dependency issues or other general incompatibilities. You may rely on server-only packages such as the above to perform logic in access control functions, hooks, and other contexts (which is great!) but when you boot up your Payload app and try to view it in the browser, you'll likely run into missing dependency issues or other general incompatibilities.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br/> <strong>Tip:</strong>
To avoid problems with server code making it to your Webpack bundle, you can use the <strong>alias</strong> Webpack feature to tell Webpack to avoid importing the modules you want to restrict to server-only. <br />
To avoid problems with server code making it to your Webpack bundle, you can use the{' '}
<strong>alias</strong> Webpack feature to tell Webpack to avoid importing the modules you want to
restrict to server-only.
</Banner> </Banner>
<strong>For example, let's say that you have a Collection called `Subscriptions` which relies on Stripe:</strong> <strong>
For example, let's say that you have a Collection called `Subscriptions` which relies on Stripe:
</strong>
<br/><br/> <br />
<br />
`collections/Subscriptions/index.js` `collections/Subscriptions/index.js`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
import createStripeSubscription from './hooks/createStripeSubscription'; import createStripeSubscription from './hooks/createStripeSubscription'
export const Subscription: CollectionConfig = { export const Subscription: CollectionConfig = {
slug: 'subscriptions', slug: 'subscriptions',
hooks: { hooks: {
beforeChange: [ beforeChange: [createStripeSubscription],
createStripeSubscription, },
] fields: [
}, {
fields: [ name: 'stripeSubscriptionID',
{ type: 'text',
name: 'stripeSubscriptionID', required: true,
type: 'text', },
required: true, ],
} }
]
};
``` ```
The collection above features a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload. The collection above features a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload.
<strong>That hook might look something like this:</strong> <strong>That hook might look something like this:</strong>
<br/><br/> <br />
<br />
`collections/Subscriptions/hooks/createStripeSubscription.js` `collections/Subscriptions/hooks/createStripeSubscription.js`
```js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); ```js
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
const createStripeSubscription = async ({ data, operation }) => { const createStripeSubscription = async ({ data, operation }) => {
if (operation === 'create') { if (operation === 'create') {
const dataWithStripeID = {...data}; const dataWithStripeID = { ...data }
// use Stripe to create a Stripe subscription // use Stripe to create a Stripe subscription
const subscription = await stripe.subscriptions.create({ const subscription = await stripe.subscriptions.create({
// Configure the subscription accordingly // Configure the subscription accordingly
}); })
// Automatically add the Stripe subscription ID // Automatically add the Stripe subscription ID
// to the data that will be saved to this Subscription doc // to the data that will be saved to this Subscription doc
dataWithStripeID.stripeSubscriptionID = subscription.id; dataWithStripeID.stripeSubscriptionID = subscription.id
return dataWithStripeID return dataWithStripeID
} }
return data; return data
} }
export default createStripeSubscription; export default createStripeSubscription
``` ```
<Banner type="error"> <Banner type="error">
<strong>Warning:</strong><br/> <strong>Warning:</strong>
The above code is NOT production-ready and should not be referenced to create Stripe subscriptions. Although creating a beforeChange hook is a completely valid spot to do things like create subscriptions, the code above is incomplete and insecure, meant for explanation purposes only. <br />
The above code is NOT production-ready and should not be referenced to create Stripe
subscriptions. Although creating a beforeChange hook is a completely valid spot to do things like
create subscriptions, the code above is incomplete and insecure, meant for explanation purposes
only.
</Banner> </Banner>
**As-is, this collection will prevent your Admin panel from bundling or loading correctly, because Stripe relies on some Node-only packages.** **As-is, this collection will prevent your Admin panel from bundling or loading correctly, because Stripe relies on some Node-only packages.**
@@ -117,52 +129,61 @@ export default createStripeSubscription;
To remedy this issue you can extend the Payload Webpack config to alias your entire `createStripeSubscription` hook to a separate, empty mock file. To remedy this issue you can extend the Payload Webpack config to alias your entire `createStripeSubscription` hook to a separate, empty mock file.
Example in `payload.config.js`: Example in `payload.config.js`:
```js
import { buildConfig } from 'payload/config';
import path from 'path';
import Subscription from './collections/Subscription';
const createStripeSubscriptionPath = path.resolve(__dirname, 'collections/Subscription/hooks/createStripeSubscription.js'); ```js
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js'); import { buildConfig } from 'payload/config'
import path from 'path'
import Subscription from './collections/Subscription'
const createStripeSubscriptionPath = path.resolve(
__dirname,
'collections/Subscription/hooks/createStripeSubscription.js',
)
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js')
export default buildConfig({ export default buildConfig({
collections: [ collections: [Subscription],
Subscription admin: {
], webpack: (config) => ({
admin: { ...config,
webpack: (config) => ({ resolve: {
...config, ...config.resolve,
resolve: { alias: {
...config.resolve, ...config.resolve.alias,
alias: { [createStripeSubscriptionPath]: mockModulePath,
...config.resolve.alias, },
[createStripeSubscriptionPath]: mockModulePath, },
} }),
} },
}) })
}
});
``` ```
The above code will alias the file at path `createStripeSubscriptionPath` to a mocked module, which might look like this: The above code will alias the file at path `createStripeSubscriptionPath` to a mocked module, which might look like this:
`mocks/emptyObject.js` `mocks/emptyObject.js`
```js ```js
export default {}; export default {}
``` ```
Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too. Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br/> <strong>Tip:</strong>
If changes to your Webpack aliases are not surfacing, they might be [cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try deleting that folder and restarting your server. <br />
If changes to your Webpack aliases are not surfacing, they might be
[cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try
deleting that folder and restarting your server.
</Banner> </Banner>
## Admin environment vars ## Admin environment vars
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong><br /> <strong>Important:</strong>
Be careful about what variables you provide to your client-side code. Analyze every single one to make sure that you're not accidentally leaking anything that an attacker could exploit. Only keys that are safe to be available to everyone in plain text should be provided to your Admin panel. <br />
Be careful about what variables you provide to your client-side code. Analyze every single one to
make sure that you're not accidentally leaking anything that an attacker could exploit. Only keys
that are safe to be available to everyone in plain text should be provided to your Admin panel.
</Banner> </Banner>
By default, `env` variables are **not** provided to the Admin panel for security and safety reasons. But, Payload provides you with a way to still provide `env` vars to your frontend code. By default, `env` variables are **not** provided to the Admin panel for security and safety reasons. But, Payload provides you with a way to still provide `env` vars to your frontend code.

View File

@@ -41,8 +41,8 @@ Technically, both of these options will work for third-party integrations but th
To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection. To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection.
<Banner type="success"> <Banner type="success">
User API keys are encrypted within the database, meaning that if your database User API keys are encrypted within the database, meaning that if your database is compromised,
is compromised, your API keys will not be. your API keys will not be.
</Banner> </Banner>
#### Authenticating via API Key #### Authenticating via API Key
@@ -52,31 +52,31 @@ To authenticate REST or GraphQL API requests using an API key, set the `Authoriz
**For example, using Fetch:** **For example, using Fetch:**
```ts ```ts
import User from '../collections/User'; import User from '../collections/User'
const response = await fetch("http://localhost:3000/api/pages", { const response = await fetch('http://localhost:3000/api/pages', {
headers: { headers: {
Authorization: `${User.slug} API-Key ${YOUR_API_KEY}`, Authorization: `${User.slug} API-Key ${YOUR_API_KEY}`,
}, },
}); })
``` ```
Payload ensures that the same, uniform access control is used across all authentication strategies. This enables you to utilize your existing access control configurations with both API keys and the standard email/password authentication. This consistency can aid in maintaining granular control over your API keys. Payload ensures that the same, uniform access control is used across all authentication strategies. This enables you to utilize your existing access control configurations with both API keys and the standard email/password authentication. This consistency can aid in maintaining granular control over your API keys.
#### API Key *Only* Authentication #### API Key _Only_ Authentication
If you want to use API keys as the only authentication method for a collection, you can disable the default local strategy by setting `disableLocalStrategy` to `true` on the collection's `auth` property. This will disable the ability to authenticate with email and password, and will only allow for authentication via API key. If you want to use API keys as the only authentication method for a collection, you can disable the default local strategy by setting `disableLocalStrategy` to `true` on the collection's `auth` property. This will disable the ability to authenticate with email and password, and will only allow for authentication via API key.
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Customers: CollectionConfig = { export const Customers: CollectionConfig = {
slug: 'customers', slug: 'customers',
auth: { auth: {
useAPIKey: true, useAPIKey: true,
disableLocalStrategy: true, disableLocalStrategy: true,
} },
}; }
``` ```
### Forgot Password ### Forgot Password
@@ -90,17 +90,16 @@ Function that accepts one argument, containing `{ req, token, user }`, that allo
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br /> <br />
HTML templating can be used to create custom email templates, inline CSS HTML templating can be used to create custom email templates, inline CSS automatically, and more.
automatically, and more. You can make a reusable function that standardizes You can make a reusable function that standardizes all email sent from Payload, which makes
all email sent from Payload, which makes sending custom emails more DRY. sending custom emails more DRY. Payload doesn't ship with an HTML templating engine, so you are
Payload doesn't ship with an HTML templating engine, so you are free to choose free to choose your own.
your own.
</Banner> </Banner>
Example: Example:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Customers: CollectionConfig = { export const Customers: CollectionConfig = {
slug: 'customers', slug: 'customers',
@@ -109,7 +108,7 @@ export const Customers: CollectionConfig = {
// highlight-start // highlight-start
generateEmailHTML: ({ req, token, user }) => { generateEmailHTML: ({ req, token, user }) => {
// Use the token provided to allow your user to reset their password // Use the token provided to allow your user to reset their password
const resetPasswordURL = `https://yourfrontend.com/reset-password?token=${token}`; const resetPasswordURL = `https://yourfrontend.com/reset-password?token=${token}`
return ` return `
<!doctype html> <!doctype html>
@@ -123,22 +122,21 @@ export const Customers: CollectionConfig = {
</p> </p>
</body> </body>
</html> </html>
`; `
} },
// highlight-end // highlight-end
} },
} },
}; }
``` ```
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
If you specify a different URL to send your users to for resetting their If you specify a different URL to send your users to for resetting their password, such as a page
password, such as a page on the frontend of your app or similar, you need to on the frontend of your app or similar, you need to handle making the call to the Payload REST or
handle making the call to the Payload REST or GraphQL reset-password operation GraphQL reset-password operation yourself on your frontend, using the token that was provided for
yourself on your frontend, using the token that was provided for you. Above, you. Above, it was passed via query parameter.
it was passed via query parameter.
</Banner> </Banner>
**`generateEmailSubject`** **`generateEmailSubject`**
@@ -173,8 +171,7 @@ Function that accepts one argument, containing `{ req, token, user }`, that allo
Example: Example:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Customers: CollectionConfig = { export const Customers: CollectionConfig = {
slug: 'customers', slug: 'customers',
@@ -183,24 +180,23 @@ export const Customers: CollectionConfig = {
// highlight-start // highlight-start
generateEmailHTML: ({ req, token, user }) => { generateEmailHTML: ({ req, token, user }) => {
// Use the token provided to allow your user to verify their account // Use the token provided to allow your user to verify their account
const url = `https://yourfrontend.com/verify?token=${token}`; const url = `https://yourfrontend.com/verify?token=${token}`
return `Hey ${user.email}, verify your email by clicking here: ${url}`; return `Hey ${user.email}, verify your email by clicking here: ${url}`
} },
// highlight-end // highlight-end
} },
} },
}; }
``` ```
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
If you specify a different URL to send your users to for email verification, If you specify a different URL to send your users to for email verification, such as a page on the
such as a page on the frontend of your app or similar, you need to handle frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL
making the call to the Payload REST or GraphQL verification operation yourself verification operation yourself on your frontend, using the token that was provided for you.
on your frontend, using the token that was provided for you. Above, it was Above, it was passed via query parameter.
passed via query parameter.
</Banner> </Banner>
**`generateEmailSubject`** **`generateEmailSubject`**
@@ -231,9 +227,8 @@ As of Payload `1.0.0`, you can add additional authentication strategies to Paylo
Behind the scenes, Payload uses PassportJS to power its local authentication strategy, so most strategies listed on the PassportJS website will work seamlessly. Combined with adding custom components to the admin panel's `Login` view, you can create advanced authentication strategies directly within Payload. Behind the scenes, Payload uses PassportJS to power its local authentication strategy, so most strategies listed on the PassportJS website will work seamlessly. Combined with adding custom components to the admin panel's `Login` view, you can create advanced authentication strategies directly within Payload.
<Banner type="warning"> <Banner type="warning">
This is an advanced feature, so only attempt this if you are an experienced This is an advanced feature, so only attempt this if you are an experienced developer. Otherwise,
developer. Otherwise, just let Payload's built-in authentication handle user just let Payload's built-in authentication handle user auth for you.
auth for you.
</Banner> </Banner>
The `strategies` property is an array that takes objects with the following properties: The `strategies` property is an array that takes objects with the following properties:
@@ -250,7 +245,6 @@ However, if you pass a function to `strategy`, `name` is a required property.
In either case, Payload will prefix the strategy name with the collection `slug` that the strategy is passed to. In either case, Payload will prefix the strategy name with the collection `slug` that the strategy is passed to.
### Admin autologin ### Admin autologin
For testing and demo purposes you may want to skip forcing the admin user to login in order to access the panel. For testing and demo purposes you may want to skip forcing the admin user to login in order to access the panel.
@@ -259,29 +253,33 @@ The default is that all users will have to login and this should not be enabled
#### autoLogin Options #### autoLogin Options
| Option | Description | | Option | Description |
| -------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------| | ----------------- | --------------------------------------------------------------------------------------------------------------- |
| **`email`** | The email address of the user to login as | | **`email`** | The email address of the user to login as |
| **`password`** | The password of the user to login as | | **`password`** | The password of the user to login as |
| **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. | | **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. |
The recommended way to use this feature is behind an environment variable to ensure it is disabled when in production. The recommended way to use this feature is behind an environment variable to ensure it is disabled when in production.
**Example:** **Example:**
```ts ```ts
export default buildConfig({ export default buildConfig({
admin: { admin: {
user: 'users', user: 'users',
// highlight-start // highlight-start
autoLogin: process.env.PAYLOAD_PUBLIC_ENABLE_AUTOLOGIN === 'true' ? { autoLogin:
email: 'test@example.com', process.env.PAYLOAD_PUBLIC_ENABLE_AUTOLOGIN === 'true'
password: 'test', ? {
prefillOnly: true, email: 'test@example.com',
} : false, password: 'test',
prefillOnly: true,
}
: false,
// highlight-end // highlight-end
}, },
collections: [ /** */], collections: [
/** */
],
}) })
``` ```

View File

@@ -17,6 +17,7 @@ The Access operation returns what a logged in user can and can't do with the col
`GET http://localhost:3000/api/access` `GET http://localhost:3000/api/access`
Example response: Example response:
```ts ```ts
{ {
canAccessAdmin: true, canAccessAdmin: true,
@@ -77,6 +78,7 @@ Returns either a logged in user with token or null when there is no logged in us
`GET http://localhost:3000/api/[collection-slug]/me` `GET http://localhost:3000/api/[collection-slug]/me`
Example response: Example response:
```ts ```ts
{ {
user: { // The JWT "payload" ;) from the logged in user user: { // The JWT "payload" ;) from the logged in user
@@ -108,6 +110,7 @@ query {
Accepts an `email` and `password`. On success, it will return the logged in user as well as a token that can be used to authenticate. In the GraphQL and REST APIs, this operation also automatically sets an HTTP-only cookie including the user's token. If you pass an Express `res` to the Local API operation, Payload will set a cookie there as well. Accepts an `email` and `password`. On success, it will return the logged in user as well as a token that can be used to authenticate. In the GraphQL and REST APIs, this operation also automatically sets an HTTP-only cookie including the user's token. If you pass an Express `res` to the Local API operation, Payload will set a cookie there as well.
**Example REST API login**: **Example REST API login**:
```ts ```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/login', { const res = await fetch('http://localhost:3000/api/[collection-slug]/login', {
method: 'POST', method: 'POST',
@@ -117,10 +120,10 @@ const res = await fetch('http://localhost:3000/api/[collection-slug]/login', {
body: JSON.stringify({ body: JSON.stringify({
email: 'dev@payloadcms.com', email: 'dev@payloadcms.com',
password: 'this-is-not-our-password...or-is-it?', password: 'this-is-not-our-password...or-is-it?',
}) }),
}) })
const json = await res.json(); const json = await res.json()
// JSON will be equal to the following: // JSON will be equal to the following:
/* /*
@@ -168,6 +171,7 @@ const result = await payload.login({
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way. As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
**Example REST API logout**: **Example REST API logout**:
```ts ```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', { const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
method: 'POST', method: 'POST',
@@ -194,6 +198,7 @@ This operation requires a non-expired token to send back a new one. If the user'
If successful, this operation will automatically renew the user's HTTP-only cookie and will send back the updated token in JSON. If successful, this operation will automatically renew the user's HTTP-only cookie and will send back the updated token in JSON.
**Example REST API token refresh**: **Example REST API token refresh**:
```ts ```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', { const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', {
method: 'POST', method: 'POST',
@@ -202,7 +207,7 @@ const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-tok
}, },
}) })
const json = await res.json(); const json = await res.json()
// JSON will be equal to the following: // JSON will be equal to the following:
/* /*
@@ -233,7 +238,10 @@ mutation {
``` ```
<Banner type="success"> <Banner type="success">
The Refresh operation will automatically find the user's token in either a JWT header or the HTTP-only cookie. But, you can specify the token you're looking to refresh by providing the REST API with a `token` within the JSON body of the request, or by providing the GraphQL resolver a `token` arg. The Refresh operation will automatically find the user's token in either a JWT header or the
HTTP-only cookie. But, you can specify the token you're looking to refresh by providing the REST
API with a `token` within the JSON body of the request, or by providing the GraphQL resolver a
`token` arg.
</Banner> </Banner>
### Verify by Email ### Verify by Email
@@ -241,13 +249,14 @@ mutation {
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API. If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
**Example REST API user verification**: **Example REST API user verification**:
```ts ```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, { const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}); })
``` ```
**Example GraphQL Mutation**: **Example GraphQL Mutation**:
@@ -274,6 +283,7 @@ If a user locks themselves out and you wish to deliberately unlock them, you can
To restrict who is allowed to unlock users, you can utilize the [`unlock`](/docs/access-control/overview#unlock) access control function. To restrict who is allowed to unlock users, you can utilize the [`unlock`](/docs/access-control/overview#unlock) access control function.
**Example REST API unlock**: **Example REST API unlock**:
```ts ```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/unlock`, { const res = await fetch(`http://localhost:3000/api/[collection-slug]/unlock`, {
method: 'POST', method: 'POST',
@@ -308,6 +318,7 @@ The link to reset the user's password contains a token which is what allows the
By default, the Forgot Password operations send users to the Payload Admin panel to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/config#forgot-password). By default, the Forgot Password operations send users to the Payload Admin panel to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/config#forgot-password).
**Example REST API Forgot Password**: **Example REST API Forgot Password**:
```ts ```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, { const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, {
method: 'POST', method: 'POST',
@@ -317,7 +328,7 @@ const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-pass
body: JSON.stringify({ body: JSON.stringify({
email: 'dev@payloadcms.com', email: 'dev@payloadcms.com',
}), }),
}); })
``` ```
**Example GraphQL Mutation**: **Example GraphQL Mutation**:
@@ -336,13 +347,18 @@ const token = await payload.forgotPassword({
data: { data: {
email: 'dev@payloadcms.com', email: 'dev@payloadcms.com',
}, },
disableEmail: false // you can disable the auto-generation of email via local API disableEmail: false, // you can disable the auto-generation of email via local API
}); })
``` ```
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br/> <strong>Tip:</strong>
You can stop the reset-password email from being sent via using the local API. This is helpful if you need to create user accounts programmatically, but not set their password for them. This effectively generates a reset password token which you can then use to send to a page you create, allowing a user to "complete" their account by setting their password. In the background, you'd use the token to "reset" their password. <br />
You can stop the reset-password email from being sent via using the local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
</Banner> </Banner>
### Reset Password ### Reset Password
@@ -350,6 +366,7 @@ const token = await payload.forgotPassword({
After a user has "forgotten" their password and a token is generated, that token can be used to send to the reset password operation along with a new password which will allow the user to reset their password securely. After a user has "forgotten" their password and a token is generated, that token can be used to send to the reset password operation along with a new password which will allow the user to reset their password securely.
**Example REST API Reset Password**: **Example REST API Reset Password**:
```ts ```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/reset-password`, { const res = await fetch(`http://localhost:3000/api/[collection-slug]/reset-password`, {
method: 'POST', method: 'POST',

View File

@@ -12,13 +12,14 @@ keywords: authentication, config, configuration, overview, documentation, Conten
/> />
<Banner> <Banner>
Payload provides for highly secure and customizable user Authentication out of the box, which allows for users to identify themselves to Payload. Payload provides for highly secure and customizable user Authentication out of the box, which
allows for users to identify themselves to Payload.
</Banner> </Banner>
Authentication is used within the Payload Admin panel itself as well as throughout your app(s) themselves however you determine necessary. Authentication is used within the Payload Admin panel itself as well as throughout your app(s) themselves however you determine necessary.
![Authentication admin panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg) ![Authentication admin panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg)
*Admin panel screenshot depicting an Admins Collection with Auth enabled* _Admin panel screenshot depicting an Admins Collection with Auth enabled_
**Here are some common use cases of Authentication outside of Payload's dashboard itself:** **Here are some common use cases of Authentication outside of Payload's dashboard itself:**
@@ -38,7 +39,7 @@ Every Payload Collection can opt-in to supporting Authentication by specifying t
Simple example collection: Simple example collection:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Admins: CollectionConfig = { export const Admins: CollectionConfig = {
slug: 'admins', slug: 'admins',
@@ -56,13 +57,8 @@ export const Admins: CollectionConfig = {
name: 'role', name: 'role',
type: 'select', type: 'select',
required: true, required: true,
options: [ options: ['user', 'admin', 'editor', 'developer'],
'user', },
'admin',
'editor',
'developer',
],
}
], ],
} }
``` ```
@@ -86,8 +82,11 @@ Successfully logging in returns a `JWT` (JSON web token) which is how a user wil
You can specify what data gets encoded to the JWT token by setting `saveToJWT` to true in your auth collection fields. If you wish to use a different key other than the field `name`, you can provide it to `saveToJWT` as a string. It is also possible to use `saveToJWT` on fields that are nested in inside groups and tabs. If a group has a `saveToJWT` set it will include the object with all sub-fields in the token. You can set `saveToJWT: false` for any fields you wish to omit. If a field inside a group has `saveToJWT` set, but the group does not, the field will be included at the top level of the token. You can specify what data gets encoded to the JWT token by setting `saveToJWT` to true in your auth collection fields. If you wish to use a different key other than the field `name`, you can provide it to `saveToJWT` as a string. It is also possible to use `saveToJWT` on fields that are nested in inside groups and tabs. If a group has a `saveToJWT` set it will include the object with all sub-fields in the token. You can set `saveToJWT: false` for any fields you wish to omit. If a field inside a group has `saveToJWT` set, but the group does not, the field will be included at the top level of the token.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br/> <strong>Tip:</strong>
You can access the logged-in user from access control functions and hooks via the Express <strong>req</strong>. The logged-in user is automatically added as the <strong>user</strong> property. <br />
You can access the logged-in user from access control functions and hooks via the Express{' '}
<strong>req</strong>. The logged-in user is automatically added as the <strong>user</strong>{' '}
property.
</Banner> </Banner>
### HTTP-only cookies ### HTTP-only cookies
@@ -107,16 +106,19 @@ Fetch example, including credentials:
```ts ```ts
const response = await fetch('http://localhost:3000/api/pages', { const response = await fetch('http://localhost:3000/api/pages', {
credentials: 'include', credentials: 'include',
}); })
const pages = await response.json(); const pages = await response.json()
``` ```
For more about how to automatically include cookies in requests from your app to your Payload API, [click here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included). For more about how to automatically include cookies in requests from your app to your Payload API, [click here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br/> <strong>Tip:</strong>
To make sure you have a Payload cookie set properly in your browser after logging in, you can use Chrome's Developer Tools - Application - Cookies - [your-domain-here]. The Chrome Developer tools will still show HTTP-only cookies, even when JavaScript running on the page can't. <br />
To make sure you have a Payload cookie set properly in your browser after logging in, you can use
Chrome's Developer Tools - Application - Cookies - [your-domain-here]. The Chrome Developer tools
will still show HTTP-only cookies, even when JavaScript running on the page can't.
</Banner> </Banner>
### CSRF Protection ### CSRF Protection
@@ -128,28 +130,33 @@ For example, let's say you have a very popular app running at coolsite.com. This
So, if a user of coolsite.com is logged in and just browsing around on the internet, they might stumble onto a page with bad intentions. That bad page might automatically make requests to all sorts of sites to see if they can find one that they can log into - and coolsite.com might be on their list. If your user was logged in while they visited that evil site, the attacker could do whatever they wanted as if they were your coolsite.com user by just sending requests to the coolsite API (which would automatically include the auth cookie). They could send themselves a bunch of money from your user's account, change the user's password, etc. This is what a CSRF attack is. So, if a user of coolsite.com is logged in and just browsing around on the internet, they might stumble onto a page with bad intentions. That bad page might automatically make requests to all sorts of sites to see if they can find one that they can log into - and coolsite.com might be on their list. If your user was logged in while they visited that evil site, the attacker could do whatever they wanted as if they were your coolsite.com user by just sending requests to the coolsite API (which would automatically include the auth cookie). They could send themselves a bunch of money from your user's account, change the user's password, etc. This is what a CSRF attack is.
<Banner type="warning"> <Banner type="warning">
<strong>To protect against CSRF attacks, Payload only accepts cookie-based authentication from domains that you explicitly whitelist.</strong> <strong>
To protect against CSRF attacks, Payload only accepts cookie-based authentication from domains
that you explicitly whitelist.
</strong>
</Banner> </Banner>
To define domains that should allow users to identify themselves via the Payload HTTP-only cookie, use the `csrf` option on the base Payload config to whitelist domains that you trust. To define domains that should allow users to identify themselves via the Payload HTTP-only cookie, use the `csrf` option on the base Payload config to whitelist domains that you trust.
`payload.config.ts`: `payload.config.ts`:
```ts ```ts
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
const config = buildConfig({ const config = buildConfig({
collections: [ collections: [
// collections here // collections here
], ],
// highlight-start // highlight-start
csrf: [ // whitelist of domains to allow cookie auth from csrf: [
// whitelist of domains to allow cookie auth from
'https://your-frontend-app.com', 'https://your-frontend-app.com',
'https://your-other-frontend-app.com', 'https://your-other-frontend-app.com',
], ],
// highlight-end // highlight-end
}); })
export default config; export default config
``` ```
### Identifying users via the Authorization Header ### Identifying users via the Authorization Header
@@ -157,11 +164,12 @@ export default config;
In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request. In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request.
Example: Example:
```ts ```ts
const request = await fetch('http://localhost:3000', { const request = await fetch('http://localhost:3000', {
headers: { headers: {
Authorization: `JWT ${token}` Authorization: `JWT ${token}`,
} },
}) })
``` ```

View File

@@ -11,48 +11,47 @@ Because Payload uses your existing Express server, you are free to add whatever
This approach has a ton of benefits - it's great for isolation of concerns and limiting scope, but it also means that your additional routes won't have access to Payload's user authentication. This approach has a ton of benefits - it's great for isolation of concerns and limiting scope, but it also means that your additional routes won't have access to Payload's user authentication.
<Banner type="success"> <Banner type="success">
You can make full use of Payload's built-in authentication within your own You can make full use of Payload's built-in authentication within your own custom Express
custom Express endpoints by adding Payload's authentication middleware. endpoints by adding Payload's authentication middleware.
</Banner> </Banner>
<Banner type="warning"> <Banner type="warning">
Payload must be initialized before the `payload.authenticate` middleware can Payload must be initialized before the `payload.authenticate` middleware can be used. This is done
be used. This is done by calling `payload.init()` prior to adding the by calling `payload.init()` prior to adding the middleware.
middleware.
</Banner> </Banner>
Example in `server.js`: Example in `server.js`:
```ts ```ts
import express from "express"; import express from 'express'
import payload from "payload"; import payload from 'payload'
const app = express(); const app = express()
const start = async () => { const start = async () => {
await payload.init({ await payload.init({
secret: "PAYLOAD_SECRET_KEY", secret: 'PAYLOAD_SECRET_KEY',
mongoURL: "mongodb://localhost/payload", mongoURL: 'mongodb://localhost/payload',
express: app, express: app,
}); })
const router = express.Router(); const router = express.Router()
// Note: Payload must be initialized before the `payload.authenticate` middleware can be used // Note: Payload must be initialized before the `payload.authenticate` middleware can be used
router.use(payload.authenticate); // highlight-line router.use(payload.authenticate) // highlight-line
router.get("/", (req, res) => { router.get('/', (req, res) => {
if (req.user) { if (req.user) {
return res.send(`Authenticated successfully as ${req.user.email}.`); return res.send(`Authenticated successfully as ${req.user.email}.`)
} }
return res.send("Not authenticated"); return res.send('Not authenticated')
}); })
app.use("/some-route-here", router); app.use('/some-route-here', router)
app.listen(3000); app.listen(3000)
}; }
start(); start()
``` ```

View File

@@ -11,10 +11,10 @@ keywords: configuration, config, settings, project, cloud, payload cloud, deploy
Once you have created a project, you will need to select your plan. This will determine the resources that are allocated to your project and the features that are available to you. Once you have created a project, you will need to select your plan. This will determine the resources that are allocated to your project and the features that are available to you.
<Banner type="success"> <Banner type="success">
Note: All Payload Cloud teams that deploy a project require a card on file. Note: All Payload Cloud teams that deploy a project require a card on file. This helps us prevent
This helps us prevent fraud and abuse on our platform. If you select a plan fraud and abuse on our platform. If you select a plan with a free trial, you will not be charged
with a free trial, you will not be charged until your trial period is over. until your trial period is over. Well remind you 7 days before your trial ends and you can cancel
Well remind you 7 days before your trial ends and you can cancel anytime. anytime.
</Banner> </Banner>
### Project Details ### Project Details
@@ -44,8 +44,8 @@ If you are deploying a new project from a template, the following settings will
Any of the features in Payload Cloud that require environment variables will automatically be provided to your application. If your app requires any custom environment variables, you can set them here. Any of the features in Payload Cloud that require environment variables will automatically be provided to your application. If your app requires any custom environment variables, you can set them here.
<Banner type="warning"> <Banner type="warning">
Note: For security reasons, any variables you wish to provide to the Admin Note: For security reasons, any variables you wish to provide to the Admin panel must be prefixed
panel must be prefixed with `PAYLOAD_PUBLIC_`.  Learn more with `PAYLOAD_PUBLIC_`.  Learn more
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars). [here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
</Banner> </Banner>
@@ -54,9 +54,8 @@ Any of the features in Payload Cloud that require environment variables will aut
Payment methods can be set per project and can be updated any time. You can use teams default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings. Payment methods can be set per project and can be updated any time. You can use teams default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings.
<Banner type="success"> <Banner type="success">
<strong>Note:</strong> All Payload Cloud teams that deploy a project require a <strong>Note:</strong> All Payload Cloud teams that deploy a project require a card on file. This
card on file. This helps us prevent fraud and abuse on our platform. If you helps us prevent fraud and abuse on our platform. If you select a plan with a free trial, you will
select a plan with a free trial, you will not be charged until your trial not be charged until your trial period is over. Well remind you 7 days before your trial ends and
period is over. Well remind you 7 days before your trial ends and you can you can cancel anytime.
cancel anytime.
</Banner> </Banner>

View File

@@ -13,9 +13,8 @@ Payload Cloud offers various plans tailored to meet your specific needs, includi
To get started, you first need to create an account. Head over to [the login screen](https://payloadcms.com/login) and **Register for Free**. To get started, you first need to create an account. Head over to [the login screen](https://payloadcms.com/login) and **Register for Free**.
<Banner type="success"> <Banner type="success">
To create your first project, you can either select [a To create your first project, you can either select [a template](#starting-from-a-template) or
template](#starting-from-a-template) or [import an existing [import an existing project](#importing-from-an-existing-codebase) from GitHub.
project](#importing-from-an-existing-codebase) from GitHub.
</Banner> </Banner>
## Starting from a Template ## Starting from a Template
@@ -32,9 +31,8 @@ Next, select your `GitHub Scope`. If you belong to multiple organizations, they
After selecting your scope, create a unique `repository name` and select whether you want your repository to be public or private on GitHub. After selecting your scope, create a unique `repository name` and select whether you want your repository to be public or private on GitHub.
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> Public repositories can be accessed by anyone online, <strong>Note:</strong> Public repositories can be accessed by anyone online, while private
while private repositories grant access only to you and anyone you explicitly repositories grant access only to you and anyone you explicitly authorize.
authorize.
</Banner> </Banner>
Once you are ready, click **Create Project**. This will clone the selected template to a new repository in your GitHub account, and take you to the configuration page to set up your project for deployment. Once you are ready, click **Create Project**. This will clone the selected template to a new repository in your GitHub account, and take you to the configuration page to set up your project for deployment.
@@ -47,7 +45,7 @@ Payload Cloud works for any Node.js + MongoDB app. From the New Project page, se
_Creating a new project from an existing repository._ _Creating a new project from an existing repository._
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> In order to make use of the features of Payload Cloud <strong>Note:</strong> In order to make use of the features of Payload Cloud in your own codebase,
in your own codebase, you will need to add the [Cloud you will need to add the [Cloud Plugin](https://github.com/payloadcms/plugin-cloud) to your
Plugin](https://github.com/payloadcms/plugin-cloud) to your Payload app. Payload app.
</Banner> </Banner>

View File

@@ -9,11 +9,10 @@ keywords: cloud, payload cloud, projects, project, overview, database, file stor
### Overview ### Overview
<Banner> <Banner>
The overview tab shows your most recent deployment, along with build and The overview tab shows your most recent deployment, along with build and deployment logs. From
deployment logs. From here, you can see your live URL, deployment details like here, you can see your live URL, deployment details like timestamps and commit hash, as well as
timestamps and commit hash, as well as the status of your deployment. You can the status of your deployment. You can also trigger a redeployment manually, which will rebuild
also trigger a redeployment manually, which will rebuild your project using your project using the current configuration.
the current configuration.
</Banner> </Banner>
![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg) ![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg)
@@ -40,8 +39,8 @@ You can update settings from your Projects Settings tab. Changes to your buil
From the Environment Variables page of the Settings tab, you can add, update and delete variables for use in your project. Like build settings, these changes will trigger a redeployment of your project. From the Environment Variables page of the Settings tab, you can add, update and delete variables for use in your project. Like build settings, these changes will trigger a redeployment of your project.
<Banner> <Banner>
Note: For security reasons, any variables you wish to provide to the Admin Note: For security reasons, any variables you wish to provide to the Admin panel must be prefixed
panel must be prefixed with `PAYLOAD_PUBLIC_`.  Learn more with `PAYLOAD_PUBLIC_`.  Learn more
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars). [here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
</Banner> </Banner>
### Custom Domains ### Custom Domains
@@ -49,9 +48,8 @@ From the Environment Variables page of the Settings tab, you can add, update and
With Payload Cloud, you can add custom domain names to your project. To do so, first go to the Domains page of the Settings tab of your project. Here you can see your default domain. To add a new domain, type in the domain name you wish to use. With Payload Cloud, you can add custom domain names to your project. To do so, first go to the Domains page of the Settings tab of your project. Here you can see your default domain. To add a new domain, type in the domain name you wish to use.
<Banner> <Banner>
Note: do not include the protocol (http:// or https://) or any routes (/page). Note: do not include the protocol (http:// or https://) or any routes (/page). Only include the
Only include the domain name and extension, and optionally a subdomain. - domain name and extension, and optionally a subdomain. - your-domain.com - backend.your-domain.com
your-domain.com - backend.your-domain.com
</Banner> </Banner>
Once you click save, a DNS record will be generated for your domain name to point to your live project. Add this record into your DNS providers records, and once the records are resolving properly (this can take 1hr to 48hrs in some cases), your domain will now to point to your live project. Once you click save, a DNS record will be generated for your domain name to point to your live project. Add this record into your DNS providers records, and once the records are resolving properly (this can take 1hr to 48hrs in some cases), your domain will now to point to your live project.
@@ -60,9 +58,9 @@ You will also need to configure your Payload project to use your specified domai
```ts ```ts
export default buildConfig({ export default buildConfig({
serverURL: "https://example.com", serverURL: 'https://example.com',
// the rest of your config, // the rest of your config,
}); })
``` ```
### Email ### Email
@@ -84,18 +82,18 @@ Projects generated from a template will come pre-configured with the official Cl
`yarn add @payloadcms/plugin-cloud` `yarn add @payloadcms/plugin-cloud`
```js ```js
import { payloadCloud } from "@payloadcms/plugin-cloud"; import { payloadCloud } from '@payloadcms/plugin-cloud'
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
plugins: [payloadCloud()], plugins: [payloadCloud()],
// rest of config // rest of config
}); })
``` ```
<Banner type="warning"> <Banner type="warning">
**Note:** If your Payload config already has an email with transport, this **Note:** If your Payload config already has an email with transport, this will take precedence
will take precedence over Payload Cloud's email service. over Payload Cloud's email service.
</Banner> </Banner>
##### **Optional configuration** ##### **Optional configuration**
@@ -106,5 +104,5 @@ If you wish to opt-out of any Payload cloud features, the plugin also accepts op
payloadCloud({ payloadCloud({
storage: false, // Disable file storage storage: false, // Disable file storage
email: false, // Disable email delivery email: false, // Disable email delivery
}); })
``` ```

View File

@@ -7,8 +7,8 @@ keywords: team, teams, billing, subscription, payment, plan, plans, cloud, paylo
--- ---
<Banner> <Banner>
Within Payload Cloud, the team management feature offers you the ability to Within Payload Cloud, the team management feature offers you the ability to manage your
manage your organization, team members, billing, and subscription settings. organization, team members, billing, and subscription settings.
</Banner> </Banner>
![Payload Cloud Team Settings](https://payloadcms.com/images/docs/cloud/team-settings.jpg) ![Payload Cloud Team Settings](https://payloadcms.com/images/docs/cloud/team-settings.jpg)

View File

@@ -12,49 +12,49 @@ It's often best practice to write your Collections in separate files and then im
## Options ## Options
| Option | Description | | Option | Description |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`slug`** * | Unique, URL-friendly string that will act as an identifier for this Collection. | | **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| **`fields`** * | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. | | **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
| **`indexes`** * | Array of database indexes to create, including compound indexes that have multiple fields. | | **`indexes`** \* | Array of database indexes to create, including compound indexes that have multiple fields. |
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. | | **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) | | **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) | | **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. | | **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. | | **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. | | **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) | | **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) | | **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. | | **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. | | **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. | | **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) | | **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
#### Simple collection example #### Simple collection example
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Orders: CollectionConfig = { export const Orders: CollectionConfig = {
slug: 'orders', slug: 'orders',
fields: [ fields: [
{ {
name: 'total', name: 'total',
type: 'number', type: 'number',
required: true, required: true,
}, },
{ {
name: 'placedBy', name: 'placedBy',
type: 'relationship', type: 'relationship',
relationTo: 'customers', relationTo: 'customers',
required: true, required: true,
} },
], ],
}; }
``` ```
#### More collection config examples #### More collection config examples
@@ -66,7 +66,7 @@ You can find an assortment of [example collection configs](https://github.com/pa
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config. You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config.
| Option | Description | | Option | Description |
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text used as a label for grouping collection and global links together in the navigation. | | `group` | Text used as a label for grouping collection and global links together in the navigation. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this collection from navigation and admin routing. | | `hidden` | Set to true or a function, called with the current user, returning true to exclude this collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) | | `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
@@ -95,37 +95,37 @@ If the function is specified, a Preview button will automatically appear in the
**Example collection with preview function:** **Example collection with preview function:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Posts: CollectionConfig = { export const Posts: CollectionConfig = {
slug: 'posts', slug: 'posts',
fields: [ fields: [
{ {
name: 'slug', name: 'slug',
type: 'text', type: 'text',
required: true, required: true,
}, },
], ],
admin: { admin: {
preview: (doc, { locale }) => { preview: (doc, { locale }) => {
if (doc?.slug) { if (doc?.slug) {
return `https://bigbird.com/preview/posts/${doc.slug}?locale=${locale}`; return `https://bigbird.com/preview/posts/${doc.slug}?locale=${locale}`
} }
return null; return null
}, },
}, },
}; }
``` ```
### Pagination ### Pagination
Here are a few options that you can specify options for pagination on a collection-by-collection basis: Here are a few options that you can specify options for pagination on a collection-by-collection basis:
| Option | Description | | Option | Description |
| --------------------------- | -------------| | -------------- | --------------------------------------------------------------------------------------------------- |
| `defaultLimit` | Integer that specifies the default per-page limit that should be used. Defaults to 10. | | `defaultLimit` | Integer that specifies the default per-page limit that should be used. Defaults to 10. |
| `limits` | Provide an array of integers to use as per-page options for admins to choose from in the List view. | | `limits` | Provide an array of integers to use as per-page options for admins to choose from in the List view. |
### Access control ### Access control
@@ -146,8 +146,10 @@ In the List view, there is a "search" box that allows you to quickly find a docu
For example, let's say you have a Posts collection with `title`, `metaDescription`, and `tags` fields - and you want all three of those fields to be searchable in the List view. You can simply add `admin.listSearchableFields: ['title', 'metaDescription', 'tags']` - and the admin UI will automatically search on those three fields plus the ID field. For example, let's say you have a Posts collection with `title`, `metaDescription`, and `tags` fields - and you want all three of those fields to be searchable in the List view. You can simply add `admin.listSearchableFields: ['title', 'metaDescription', 'tags']` - and the admin UI will automatically search on those three fields plus the ID field.
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong><br/> <strong>Note:</strong>
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields so your admin queries can remain performant. <br />
If you are adding <strong>listSearchableFields</strong>, make sure you index each of these fields
so your admin queries can remain performant.
</Banner> </Banner>
### Admin Hooks ### Admin Hooks
@@ -161,24 +163,24 @@ The `beforeDuplicate` hook is an async function that accepts an object containin
Example: Example:
```ts ```ts
import { BeforeDuplicate, CollectionConfig } from 'payload/types'; import { BeforeDuplicate, CollectionConfig } from 'payload/types'
// Your auto-generated Page type // Your auto-generated Page type
import { Page } from '../payload-types.ts'; import { Page } from '../payload-types.ts'
const beforeDuplicate: BeforeDuplicate<Page> = ({ data }) => { const beforeDuplicate: BeforeDuplicate<Page> = ({ data }) => {
return { return {
...data, ...data,
title: `${data.title} Copy`, title: `${data.title} Copy`,
uniqueField: data.uniqueField ? `${data.uniqueField}-copy` : '', uniqueField: data.uniqueField ? `${data.uniqueField}-copy` : '',
}; }
}; }
export const Page: CollectionConfig = { export const Page: CollectionConfig = {
slug: 'pages', slug: 'pages',
admin: { admin: {
hooks: { hooks: {
beforeDuplicate, beforeDuplicate,
} },
}, },
fields: [ fields: [
{ {
@@ -189,8 +191,8 @@ export const Page: CollectionConfig = {
name: 'uniqueField', name: 'uniqueField',
type: 'text', type: 'text',
unique: true, unique: true,
} },
] ],
} }
``` ```
@@ -199,14 +201,14 @@ export const Page: CollectionConfig = {
You can import collection types as follows: You can import collection types as follows:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
// This is the type used for incoming collection configs. // This is the type used for incoming collection configs.
// Only the bare minimum properties are marked as required. // Only the bare minimum properties are marked as required.
``` ```
```ts ```ts
import { SanitizedCollectionConfig } from 'payload/types'; import { SanitizedCollectionConfig } from 'payload/types'
// This is the type used after an incoming collection config is fully sanitized. // This is the type used after an incoming collection config is fully sanitized.
// Generally, this is only used internally by Payload. // Generally, this is only used internally by Payload.

View File

@@ -70,11 +70,11 @@ To customize compression options, pass an object to the Payload config's `expres
```js ```js
{ {
express: { express: {
compression: { compression: {
// settings go here // settings go here
} }
} }
} }
``` ```

View File

@@ -32,29 +32,29 @@ _\* An asterisk denotes that a property is required._
#### Simple Global example #### Simple Global example
```ts ```ts
import { GlobalConfig } from "payload/types"; import { GlobalConfig } from 'payload/types'
const Nav: GlobalConfig = { const Nav: GlobalConfig = {
slug: "nav", slug: 'nav',
fields: [ fields: [
{ {
name: "items", name: 'items',
type: "array", type: 'array',
required: true, required: true,
maxRows: 8, maxRows: 8,
fields: [ fields: [
{ {
name: "page", name: 'page',
type: "relationship", type: 'relationship',
relationTo: "pages", // "pages" is the slug of an existing collection relationTo: 'pages', // "pages" is the slug of an existing collection
required: true, required: true,
}, },
], ],
}, },
], ],
}; }
export default Nav; export default Nav
``` ```
#### Global config example #### Global config example
@@ -66,8 +66,8 @@ You can find an [example Global config](https://github.com/payloadcms/public-dem
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config. You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
| Option | Description | | Option | Description |
|--------------|-----------------------------------------------------------------------------------------------------------------------------------| | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text used as a label for grouping collection and global links together in the navigation. | | `group` | Text used as a label for grouping collection and global links together in the navigation. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. | | `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) | | `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). | | `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
@@ -87,27 +87,27 @@ If the function is specified, a Preview button will automatically appear in the
**Example global with preview function:** **Example global with preview function:**
```ts ```ts
import { GlobalConfig } from "payload/types"; import { GlobalConfig } from 'payload/types'
export const MyGlobal: GlobalConfig = { export const MyGlobal: GlobalConfig = {
slug: "my-global", slug: 'my-global',
fields: [ fields: [
{ {
name: "slug", name: 'slug',
type: "text", type: 'text',
required: true, required: true,
}, },
], ],
admin: { admin: {
preview: (doc, { locale }) => { preview: (doc, { locale }) => {
if (doc?.slug) { if (doc?.slug) {
return `https://bigbird.com/preview/${doc.slug}?locale=${locale}`; return `https://bigbird.com/preview/${doc.slug}?locale=${locale}`
} }
return null; return null
}, },
}, },
}; }
``` ```
### Access control ### Access control
@@ -127,14 +127,14 @@ Globals support all field types that Payload has to offer—including simple fie
You can import global types as follows: You can import global types as follows:
```ts ```ts
import { GlobalConfig } from "payload/types"; import { GlobalConfig } from 'payload/types'
// This is the type used for incoming global configs. // This is the type used for incoming global configs.
// Only the bare minimum properties are marked as required. // Only the bare minimum properties are marked as required.
``` ```
```ts ```ts
import { SanitizedGlobalConfig } from "payload/types"; import { SanitizedGlobalConfig } from 'payload/types'
// This is the type used after an incoming global config is fully sanitized. // This is the type used after an incoming global config is fully sanitized.
// Generally, this is only used internally by Payload. // Generally, this is only used internally by Payload.

View File

@@ -13,47 +13,47 @@ While Payload's built-in features come translated, you may want to also translat
Here is an example of a simple collection supporting both English and Spanish editors: Here is an example of a simple collection supporting both English and Spanish editors:
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const Articles: CollectionConfig = { export const Articles: CollectionConfig = {
slug: "articles", slug: 'articles',
labels: { labels: {
singular: { singular: {
en: "Article", en: 'Article',
es: "Artículo", es: 'Artículo',
}, },
plural: { plural: {
en: "Articles", en: 'Articles',
es: "Artículos", es: 'Artículos',
}, },
}, },
admin: { admin: {
group: { en: "Content", es: "Contenido" }, group: { en: 'Content', es: 'Contenido' },
}, },
fields: [ fields: [
{ {
name: "title", name: 'title',
type: "text", type: 'text',
label: { label: {
en: "Title", en: 'Title',
es: "Título", es: 'Título',
}, },
admin: { admin: {
placeholder: { en: "Enter title", es: "Introduce el título" }, placeholder: { en: 'Enter title', es: 'Introduce el título' },
}, },
}, },
{ {
name: "type", name: 'type',
type: "radio", type: 'radio',
options: [ options: [
{ {
value: "news", value: 'news',
label: { en: "News", es: "Noticias" }, label: { en: 'News', es: 'Noticias' },
}, // etc... }, // etc...
], ],
}, },
], ],
}; }
``` ```
### Admin UI ### Admin UI
@@ -62,8 +62,10 @@ The Payload admin panel reads the language settings of a user's browser and disp
After a user logs in, they can change their language selection in the `/account` view. After a user logs in, they can change their language selection in the `/account` view.
<Banner> <Banner>
<strong>Note:</strong><br /> <strong>Note:</strong>
If there is a language that Payload does not yet support, we accept code [contributions](https://github.com/payloadcms/payload/blob/master/contributing.md). <br />
If there is a language that Payload does not yet support, we accept code
[contributions](https://github.com/payloadcms/payload/blob/master/contributing.md).
</Banner> </Banner>
### Node Express ### Node Express
@@ -81,28 +83,28 @@ In your Payload config, you can add translations and customize the settings in `
**Example Payload config extending i18n:** **Example Payload config extending i18n:**
```ts ```ts
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
//... //...
i18n: { i18n: {
fallbackLng: "en", // default fallbackLng: 'en', // default
debug: false, // default debug: false, // default
resources: { resources: {
en: { en: {
custom: { custom: {
// namespace can be anything you want // namespace can be anything you want
key1: "Translation with {{variable}}", // translation key1: 'Translation with {{variable}}', // translation
}, },
// override existing translation keys // override existing translation keys
general: { general: {
dashboard: "Home", dashboard: 'Home',
}, },
}, },
}, },
}, },
//... //...
}); })
``` ```
See the i18next [configuration options](https://www.i18next.com/overview/configuration-options) to learn more. See the i18next [configuration options](https://www.i18next.com/overview/configuration-options) to learn more.

View File

@@ -22,15 +22,11 @@ export default buildConfig({
// collections go here // collections go here
], ],
localization: { localization: {
locales: [ locales: ['en', 'es', 'de'],
'en',
'es',
'de',
],
defaultLocale: 'en', defaultLocale: 'en',
fallback: true, fallback: true,
}, },
}); })
``` ```
**Example Payload config set up for localization with full locales objects:** **Example Payload config set up for localization with full locales objects:**
@@ -43,22 +39,22 @@ export default buildConfig({
// collections go here // collections go here
], ],
localization: { localization: {
locales: [ locales: [
{ {
label: "English", label: 'English',
code: "en", code: 'en',
}, },
{ {
label: "Arabic", label: 'Arabic',
code: "ar", code: 'ar',
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left) when current locale is rtl // opt-in to setting default text-alignment on Input fields to rtl (right-to-left) when current locale is rtl
rtl: true, rtl: true,
}, },
], ],
defaultLocale: "en", defaultLocale: 'en',
fallback: true, fallback: true,
}, },
}); })
``` ```
**Here is a brief explanation of each of the options available within the `localization` property:** **Here is a brief explanation of each of the options available within the `localization` property:**
@@ -96,17 +92,21 @@ With the above configuration, the `title` field will now be saved in the databas
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s and `block`s. All field types with a `name` property support the `localized` property—even the more complex field types like `array`s and `block`s.
<Banner> <Banner>
<strong>Note:</strong><br/> <strong>Note:</strong>
Enabling localization for field types that support nested fields will automatically create localized "sets" of all fields contained within the field. For example, if you have a page layout using a blocks field type, you have the choice of either localizing the full layout, by enabling localization on the top-level blocks field, or only certain fields within the layout. <br />
Enabling localization for field types that support nested fields will automatically create
localized "sets" of all fields contained within the field. For example, if you have a page layout
using a blocks field type, you have the choice of either localizing the full layout, by enabling
localization on the top-level blocks field, or only certain fields within the layout.
</Banner> </Banner>
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
When converting an existing field to or from `localized: true` the data When converting an existing field to or from `localized: true` the data structure in the document
structure in the document will change for this field and so existing data for will change for this field and so existing data for this field will be lost. Before changing the
this field will be lost. Before changing the localization setting on fields localization setting on fields with existing data, you may need to consider a field migration
with existing data, you may need to consider a field migration strategy. strategy.
</Banner> </Banner>
### Retrieving localized docs ### Retrieving localized docs
@@ -152,7 +152,9 @@ query {
``` ```
<Banner> <Banner>
In GraphQL, specifying the locale at the top level of a query will automatically apply it throughout all nested relationship fields. You can override this behavior by re-specifying locale arguments in nested related document queries. In GraphQL, specifying the locale at the top level of a query will automatically apply it
throughout all nested relationship fields. You can override this behavior by re-specifying locale
arguments in nested related document queries.
</Banner> </Banner>
##### Local API ##### Local API
@@ -170,5 +172,9 @@ const posts = await payload.find({
``` ```
<Banner type="alert"> <Banner type="alert">
<strong>Tip:</strong><br/>The REST and Local APIs can return all localization data in one request by passing 'all' or '*' as the <strong>locale</strong> parameter. The response will be structured so that field values come back as the full objects keyed for each locale instead of the single, translated value. <strong>Tip:</strong>
<br />
The REST and Local APIs can return all localization data in one request by passing 'all' or '*' as
the <strong>locale</strong> parameter. The response will be structured so that field values come
back as the full objects keyed for each locale instead of the single, translated value.
</Banner> </Banner>

View File

@@ -13,8 +13,8 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
This file is included in the Payload admin bundle, so make sure you do not This file is included in the Payload admin bundle, so make sure you do not embed any sensitive
embed any sensitive information. information.
</Banner> </Banner>
## Options ## Options
@@ -48,21 +48,21 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
#### Simple example #### Simple example
```ts ```ts
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
collections: [ collections: [
{ {
slug: "pages", slug: 'pages',
fields: [ fields: [
{ {
name: "title", name: 'title',
type: "text", type: 'text',
required: true, required: true,
}, },
{ {
name: "content", name: 'content',
type: "richText", type: 'richText',
required: true, required: true,
}, },
], ],
@@ -70,23 +70,23 @@ export default buildConfig({
], ],
globals: [ globals: [
{ {
slug: "header", slug: 'header',
fields: [ fields: [
{ {
name: "nav", name: 'nav',
type: "array", type: 'array',
fields: [ fields: [
{ {
name: "page", name: 'page',
type: "relationship", type: 'relationship',
relationTo: "pages", relationTo: 'pages',
}, },
], ],
}, },
], ],
}, },
], ],
}); })
``` ```
#### Full example config #### Full example config
@@ -120,10 +120,9 @@ project-name
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
If you use an environment variable to configure any properties that are If you use an environment variable to configure any properties that are required for the Admin
required for the Admin panel to function (ex. serverURL or any routes), you panel to function (ex. serverURL or any routes), you need to make sure that your Admin panel code
need to make sure that your Admin panel code can access it. [Click can access it. [Click here](/docs/admin/webpack#admin-environment-vars) for more info.
here](/docs/admin/webpack#admin-environment-vars) for more info.
</Banner> </Banner>
### Customizing & Automating Config Location Detection ### Customizing & Automating Config Location Detection
@@ -145,7 +144,7 @@ In addition to the above automated detection, you can specify your own location
```json ```json
{ {
"scripts": { "scripts": {
"dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js", "dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js"
} }
} }
``` ```
@@ -161,14 +160,14 @@ Payload comes with `isomorphic-fetch` installed which means that even in Node, y
You can import config types as follows: You can import config types as follows:
```ts ```ts
import { Config } from "payload/config"; import { Config } from 'payload/config'
// This is the type used for an incoming Payload config. // This is the type used for an incoming Payload config.
// Only the bare minimum properties are marked as required. // Only the bare minimum properties are marked as required.
``` ```
```ts ```ts
import { SanitizedConfig } from "payload/config"; import { SanitizedConfig } from 'payload/config'
// This is the type used after an incoming Payload config is fully sanitized. // This is the type used after an incoming Payload config is fully sanitized.
// Generally, this is only used internally by Payload. // Generally, this is only used internally by Payload.

View File

@@ -23,7 +23,8 @@ Database transactions allow your application to make a series of database change
By default, Payload will use transactions for all operations, as long as it is supported by the configured database. Database changes are contained within all Payload operations and any errors thrown will result in all changes being rolled back without being committed. When transactions are not supported by the database, Payload will continue to operate as expected without them. By default, Payload will use transactions for all operations, as long as it is supported by the configured database. Database changes are contained within all Payload operations and any errors thrown will result in all changes being rolled back without being committed. When transactions are not supported by the database, Payload will continue to operate as expected without them.
<Banner type="info"> <Banner type="info">
<strong>Note:</strong><br/> <strong>Note:</strong>
<br />
MongoDB requires a connection to a replicaset in order to make use of transactions. MongoDB requires a connection to a replicaset in order to make use of transactions.
</Banner> </Banner>
@@ -37,12 +38,13 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
collection: 'my-slug', collection: 'my-slug',
data: { data: {
some: 'data', some: 'data',
} },
}); })
} }
``` ```
### Async Hooks with Transactions ### Async Hooks with Transactions
Since Payload hooks can be async and be written to not await the result, it is possible to have an incorrect success response returned on a request that is rolled back. If you have a hook where you do not `await` the result, then you should **not** pass the `req.transactionID`. Since Payload hooks can be async and be written to not await the result, it is possible to have an incorrect success response returned on a request that is rolled back. If you have a hook where you do not `await` the result, then you should **not** pass the `req.transactionID`.
```ts ```ts
@@ -53,7 +55,7 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
collection: 'my-slug', collection: 'my-slug',
data: { data: {
some: 'other data', some: 'other data',
} },
}) })
// Should this call fail, it will not rollback other changes // Should this call fail, it will not rollback other changes
@@ -65,7 +67,7 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
collection: 'my-slug', collection: 'my-slug',
data: { data: {
some: 'other data', some: 'other data',
} },
}) })
} }
``` ```
@@ -82,4 +84,3 @@ The following functions can be used for managing transactions:
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes. `payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
## Migrations ## Migrations

View File

@@ -57,15 +57,16 @@ payload.init({
rejectUnauthorized: false, rejectUnauthorized: false,
}, },
}, },
fromName: "hello", fromName: 'hello',
fromAddress: "hello@example.com", fromAddress: 'hello@example.com',
}, },
// ... // ...
}); })
``` ```
<Banner type="warning"> <Banner type="warning">
It is best practice to avoid saving credentials or API keys directly in your code, use [environment variables](/docs/configuration/overview#using-environment-variables-in-your-config). It is best practice to avoid saving credentials or API keys directly in your code, use
[environment variables](/docs/configuration/overview#using-environment-variables-in-your-config).
</Banner> </Banner>
### Use an email service ### Use an email service
@@ -73,10 +74,10 @@ payload.init({
Many third party mail providers are available and offer benefits beyond basic SMTP. As an example your payload init could look this if you wanted to use SendGrid.com though the same approach would work for any other [NodeMailer transports](https://nodemailer.com/transports/) shown here or provided by another third party. Many third party mail providers are available and offer benefits beyond basic SMTP. As an example your payload init could look this if you wanted to use SendGrid.com though the same approach would work for any other [NodeMailer transports](https://nodemailer.com/transports/) shown here or provided by another third party.
```ts ```ts
import payload from "payload"; import payload from 'payload'
import nodemailerSendgrid from "nodemailer-sendgrid"; import nodemailerSendgrid from 'nodemailer-sendgrid'
const sendGridAPIKey = process.env.SENDGRID_API_KEY; const sendGridAPIKey = process.env.SENDGRID_API_KEY
payload.init({ payload.init({
...(sendGridAPIKey ...(sendGridAPIKey
@@ -85,12 +86,12 @@ payload.init({
transportOptions: nodemailerSendgrid({ transportOptions: nodemailerSendgrid({
apiKey: sendGridAPIKey, apiKey: sendGridAPIKey,
}), }),
fromName: "Admin", fromName: 'Admin',
fromAddress: "admin@example.com", fromAddress: 'admin@example.com',
}, },
} }
: {}), : {}),
}); })
``` ```
### Use a custom NodeMailer transport ### Use a custom NodeMailer transport
@@ -98,11 +99,11 @@ payload.init({
To take full control of the mail transport you may wish to use `nodemailer.createTransport()` on your server and provide it to Payload init. To take full control of the mail transport you may wish to use `nodemailer.createTransport()` on your server and provide it to Payload init.
```ts ```ts
import payload from "payload"; import payload from 'payload'
import nodemailer from "nodemailer"; import nodemailer from 'nodemailer'
const payload = require("payload"); const payload = require('payload')
const nodemailer = require("nodemailer"); const nodemailer = require('nodemailer')
const transport = await nodemailer.createTransport({ const transport = await nodemailer.createTransport({
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
@@ -111,16 +112,16 @@ const transport = await nodemailer.createTransport({
user: process.env.SMTP_USER, user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS, pass: process.env.SMTP_PASS,
}, },
}); })
payload.init({ payload.init({
email: { email: {
fromName: "Admin", fromName: 'Admin',
fromAddress: "admin@example.com", fromAddress: 'admin@example.com',
transport, transport,
}, },
// ... // ...
}); })
``` ```
### Sending Mail ### Sending Mail
@@ -136,12 +137,12 @@ To see ethereal credentials, add `logMockCredentials: true` to the email options
```ts ```ts
payload.init({ payload.init({
email: { email: {
fromName: "Admin", fromName: 'Admin',
fromAddress: "admin@example.com", fromAddress: 'admin@example.com',
logMockCredentials: true, // Optional logMockCredentials: true, // Optional
}, },
// ... // ...
}); })
``` ```
**Console output when starting payload with a mock email instance and logMockCredentials: true** **Console output when starting payload with a mock email instance and logMockCredentials: true**
@@ -160,7 +161,8 @@ payload.init({
The mock email handler is used when payload is started with neither `transport` or `transportOptions` to know how to deliver email. The mock email handler is used when payload is started with neither `transport` or `transportOptions` to know how to deliver email.
<Banner type="warning"> <Banner type="warning">
The randomly generated email account username and password will be different each time the Payload server starts. The randomly generated email account username and password will be different each time the Payload
server starts.
</Banner> </Banner>
### Using multiple mail providers ### Using multiple mail providers

View File

@@ -7,9 +7,8 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
--- ---
<Banner> <Banner>
The Array field type is used when you need to have a set of "repeating" The Array field type is used when you need to have a set of "repeating" fields. It stores an array
fields. It stores an array of objects containing the fields that you define. of objects containing the fields that you define. Its uses can be simple or highly complex.
Its uses can be simple or highly complex.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
@@ -61,47 +60,47 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "slider", // required name: 'slider', // required
type: "array", // required type: 'array', // required
label: "Image Slider", label: 'Image Slider',
minRows: 2, minRows: 2,
maxRows: 10, maxRows: 10,
interfaceName: "CardSlider", // optional interfaceName: 'CardSlider', // optional
labels: { labels: {
singular: "Slide", singular: 'Slide',
plural: "Slides", plural: 'Slides',
}, },
fields: [ fields: [
// required // required
{ {
name: "title", name: 'title',
type: "text", type: 'text',
}, },
{ {
name: "image", name: 'image',
type: "upload", type: 'upload',
relationTo: "media", relationTo: 'media',
required: true, required: true,
}, },
{ {
name: "caption", name: 'caption',
type: "text", type: 'text',
}, },
], ],
admin: { admin: {
components: { components: {
RowLabel: ({ data, index }) => { RowLabel: ({ data, index }) => {
return data?.title || `Slide ${String(index).padStart(2, "0")}`; return data?.title || `Slide ${String(index).padStart(2, '0')}`
}, },
}, },
}, },
}, },
], ],
}; }
``` ```

View File

@@ -7,10 +7,10 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
--- ---
<Banner> <Banner>
The Blocks field type is <strong>incredibly powerful</strong> and can be used The Blocks field type is <strong>incredibly powerful</strong> and can be used as a{' '}
as a <em>layout builder</em> as well as to define any other flexible content <em>layout builder</em> as well as to define any other flexible content model you can think of. It
model you can think of. It stores an array of objects, where each object must stores an array of objects, where each object must match the shape of one of your provided block
match the shape of one of your provided block configs. configs.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
@@ -28,22 +28,22 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
### Field config ### Field config
| Option | Description | | Option | Description |
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. | | **`label`** | Text used as the heading in the Admin panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** * | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. | | **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-level hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-level hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-level access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-level access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. | | **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._
@@ -62,12 +62,11 @@ Blocks are defined as separate configs of their own.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br /> <br />
Best practice is to define each block config in its own file, and then import Best practice is to define each block config in its own file, and then import them into your
them into your Blocks field as necessary. This way each block config can be Blocks field as necessary. This way each block config can be easily shared between fields. For
easily shared between fields. For instance, using the "layout builder" instance, using the "layout builder" example, you might want to feature a few of the same blocks
example, you might want to feature a few of the same blocks in a Post in a Post collection as well as a Page collection. Abstracting into their own files trivializes
collection as well as a Page collection. Abstracting into their own files their reusability.
trivializes their reusability.
</Banner> </Banner>
| Option | Description | | Option | Description |
@@ -97,7 +96,7 @@ The Admin panel provides each block with a `blockName` field which optionally al
`collections/ExampleCollection.js` `collections/ExampleCollection.js`
```ts ```ts
import { Block, CollectionConfig } from 'payload/types'; import { Block, CollectionConfig } from 'payload/types'
const QuoteBlock: Block = { const QuoteBlock: Block = {
slug: 'Quote', // required slug: 'Quote', // required
@@ -116,7 +115,7 @@ const QuoteBlock: Block = {
type: 'text', type: 'text',
}, },
], ],
}; }
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -132,7 +131,7 @@ export const ExampleCollection: CollectionConfig = {
], ],
}, },
], ],
}; }
``` ```
### TypeScript ### TypeScript
@@ -140,5 +139,5 @@ export const ExampleCollection: CollectionConfig = {
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's `Block` type: As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's `Block` type:
```ts ```ts
import type { Block } from 'payload/types'; import type { Block } from 'payload/types'
``` ```

View File

@@ -6,42 +6,41 @@ desc: Checkbox field types allow the developer to save a boolean value in the da
keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>The Checkbox field type saves a boolean in the database.</Banner>
The Checkbox field type saves a boolean in the database.
</Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/checkbox.png' srcLight="https://payloadcms.com/images/docs/fields/checkbox.png"
srcDark='https://payloadcms.com/images/docs/fields/checkbox-dark.png' srcDark="https://payloadcms.com/images/docs/fields/checkbox-dark.png"
alt='Checkbox field with text field in Payload admin panel' alt="Checkbox field with text field in Payload admin panel"
caption='Admin panel screenshot of Checkbox field with Text field below' caption="Admin panel screenshot of Checkbox field with Text field below"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. | | **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -51,7 +50,7 @@ export const ExampleCollection: CollectionConfig = {
type: 'checkbox', // required type: 'checkbox', // required
label: 'Click me to see fanciness', label: 'Click me to see fanciness',
defaultValue: false, defaultValue: false,
} },
] ],
}; }
``` ```

View File

@@ -7,39 +7,40 @@ desc: The Code field type will store any string in the Database. Learn how to us
keywords: code, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: code, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Code field type saves a string in the database, but provides the Admin panel with a code editor styled interface. The Code field type saves a string in the database, but provides the Admin panel with a code
editor styled interface.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/code.png' srcLight="https://payloadcms.com/images/docs/fields/code.png"
srcDark='https://payloadcms.com/images/docs/fields/code-dark.png' srcDark="https://payloadcms.com/images/docs/fields/code-dark.png"
alt='Shows a Code field in the Payload admin panel' alt="Shows a Code field in the Payload admin panel"
caption='Admin panel screenshot of a Code field' caption="Admin panel screenshot of a Code field"
/> />
This field uses the `monaco-react` editor syntax highlighting. This field uses the `monaco-react` editor syntax highlighting.
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | | **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | | **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._
@@ -47,16 +48,17 @@ _\* An asterisk denotes that a property is required._
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties: In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description | | Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`language`** | This property can be set to any language listed [here](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages). | | **`language`** | This property can be set to any language listed [here](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages). |
| **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IDiffEditorConstructionOptions.html). | | **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IDiffEditorConstructionOptions.html). |
### Example ### Example
`collections/ExampleCollection.ts `collections/ExampleCollection.ts
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -66,9 +68,9 @@ export const ExampleCollection: CollectionConfig = {
type: 'code', // required type: 'code', // required
required: true, required: true,
admin: { admin: {
language: 'javascript' language: 'javascript',
} },
} },
] ],
}; }
``` ```

View File

@@ -6,41 +6,43 @@ desc: With the Collapsible field, you can place fields within a collapsible layo
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Collapsible field is presentational-only and only affects the Admin panel. By using it, you can place fields within a nice layout component that can be collapsed / expanded. The Collapsible field is presentational-only and only affects the Admin panel. By using it, you
can place fields within a nice layout component that can be collapsed / expanded.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/collapsible.png' srcLight="https://payloadcms.com/images/docs/fields/collapsible.png"
srcDark='https://payloadcms.com/images/docs/fields/collapsible-dark.png' srcDark="https://payloadcms.com/images/docs/fields/collapsible-dark.png"
alt='Shows a Collapsible field in the Payload admin panel' alt="Shows a Collapsible field in the Payload admin panel"
caption='Admin panel screenshot of a Collapsible field' caption="Admin panel screenshot of a Collapsible field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| -------------- | ------------------------------------------------------------------------- | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`label`** * | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. | | **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
| **`fields`** * | Array of field types to nest within this Collapsible. | | **`fields`** \* | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Admin Config ### Admin Config
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties: In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description | | Option | Description |
| ---------------------- | ------------------------------- | | ------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state | | **`initCollapsed`** | Set the initial collapsed state |
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -48,7 +50,8 @@ export const ExampleCollection: CollectionConfig = {
{ {
label: ({ data }) => data?.title || 'Untitled', label: ({ data }) => data?.title || 'Untitled',
type: 'collapsible', // required type: 'collapsible', // required
fields: [ // required fields: [
// required
{ {
name: 'title', name: 'title',
type: 'text', type: 'text',
@@ -60,7 +63,7 @@ export const ExampleCollection: CollectionConfig = {
required: true, required: true,
}, },
], ],
} },
] ],
}; }
``` ```

View File

@@ -7,15 +7,15 @@ keywords: date, fields, config, configuration, documentation, Content Management
--- ---
<Banner> <Banner>
The Date field type saves a Date in the database and provides the Admin panel The Date field type saves a Date in the database and provides the Admin panel with a customizable
with a customizable time picker interface. time picker interface.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/date.png' srcLight="https://payloadcms.com/images/docs/fields/date.png"
srcDark='https://payloadcms.com/images/docs/fields/date-dark.png' srcDark="https://payloadcms.com/images/docs/fields/date-dark.png"
alt='Shows a Date field in the Payload admin panel' alt="Shows a Date field in the Payload admin panel"
caption='Admin panel screenshot of a Date field' caption="Admin panel screenshot of a Date field"
/> />
This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepicker) for the Admin panel component. This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepicker) for the Admin panel component.
@@ -75,41 +75,41 @@ When only `pickerAppearance` is set, an equivalent format will be rendered in th
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "dateOnly", name: 'dateOnly',
type: "date", type: 'date',
admin: { admin: {
date: { date: {
pickerAppearance: "dayOnly", pickerAppearance: 'dayOnly',
displayFormat: "d MMM yyy", displayFormat: 'd MMM yyy',
}, },
}, },
}, },
{ {
name: "timeOnly", name: 'timeOnly',
type: "date", type: 'date',
admin: { admin: {
date: { date: {
pickerAppearance: "timeOnly", pickerAppearance: 'timeOnly',
displayFormat: "h:mm:ss a", displayFormat: 'h:mm:ss a',
}, },
}, },
}, },
{ {
name: "monthOnly", name: 'monthOnly',
type: "date", type: 'date',
admin: { admin: {
date: { date: {
pickerAppearance: "monthOnly", pickerAppearance: 'monthOnly',
displayFormat: "MMMM yyyy", displayFormat: 'MMMM yyyy',
}, },
}, },
}, },
], ],
}; }
``` ```

View File

@@ -6,37 +6,35 @@ desc: The Email field enforces that the value provided is a valid email address.
keywords: email, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: email, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner> <Banner>The Email field enforces that the value provided is a valid email address.</Banner>
The Email field enforces that the value provided is a valid email address.
</Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/email.png' srcLight="https://payloadcms.com/images/docs/fields/email.png"
srcDark='https://payloadcms.com/images/docs/fields/email-dark.png' srcDark="https://payloadcms.com/images/docs/fields/email-dark.png"
alt='Shows an Email field in the Payload admin panel' alt="Shows an Email field in the Payload admin panel"
caption='Admin panel screenshot of an Email field' caption="Admin panel screenshot of an Email field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Admin config ### Admin config
@@ -53,8 +51,9 @@ Set this property to a string that will be used for browser autocomplete.
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -64,7 +63,7 @@ export const ExampleCollection: CollectionConfig = {
type: 'email', // required type: 'email', // required
label: 'Contact Email Address', label: 'Contact Email Address',
required: true, required: true,
} },
] ],
}; }
``` ```

View File

@@ -7,15 +7,15 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
--- ---
<Banner> <Banner>
The Group field allows fields to be nested under a common property name. It The Group field allows fields to be nested under a common property name. It also groups fields
also groups fields together visually in the Admin panel. together visually in the Admin panel.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/group.png' srcLight="https://payloadcms.com/images/docs/fields/group.png"
srcDark='https://payloadcms.com/images/docs/fields/group-dark.png' srcDark="https://payloadcms.com/images/docs/fields/group-dark.png"
alt='Shows a Group field in the Payload admin panel' alt="Shows a Group field in the Payload admin panel"
caption='Admin panel screenshot of a Group field' caption="Admin panel screenshot of a Group field"
/> />
### Config ### Config
@@ -51,27 +51,27 @@ Set this property to `true` to hide this field's gutter within the admin panel.
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "pageMeta", // required name: 'pageMeta', // required
type: "group", // required type: 'group', // required
interfaceName: "Meta", // optional interfaceName: 'Meta', // optional
fields: [ fields: [
// required // required
{ {
name: "title", name: 'title',
type: "text", type: 'text',
required: true, required: true,
minLength: 20, minLength: 20,
maxLength: 100, maxLength: 100,
}, },
{ {
name: "description", name: 'description',
type: "textarea", type: 'textarea',
required: true, required: true,
minLength: 40, minLength: 40,
maxLength: 160, maxLength: 160,
@@ -79,5 +79,5 @@ export const ExampleCollection: CollectionConfig = {
], ],
}, },
], ],
}; }
``` ```

View File

@@ -7,37 +7,38 @@ desc: The JSON field type will store any string in the Database. Learn how to us
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The JSON field type saves actual JSON in the database, which differs from the Code field that saves the value as a string in the database. The JSON field type saves actual JSON in the database, which differs from the Code field that
saves the value as a string in the database.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/json.png' srcLight="https://payloadcms.com/images/docs/fields/json.png"
srcDark='https://payloadcms.com/images/docs/fields/json-dark.png' srcDark="https://payloadcms.com/images/docs/fields/json-dark.png"
alt='Shows a JSON field in the Payload admin panel' alt="Shows a JSON field in the Payload admin panel"
caption='Admin panel screenshot of a JSON field' caption="Admin panel screenshot of a JSON field"
/> />
This field uses the `monaco-react` editor syntax highlighting. This field uses the `monaco-react` editor syntax highlighting.
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._
@@ -45,15 +46,16 @@ _\* An asterisk denotes that a property is required._
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties: In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description | | Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IDiffEditorConstructionOptions.html). | | **`editorOptions`** | Options that can be passed to the monaco editor, [view the full list](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IDiffEditorConstructionOptions.html). |
### Example ### Example
`collections/ExampleCollection.ts `collections/ExampleCollection.ts
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -62,7 +64,7 @@ export const ExampleCollection: CollectionConfig = {
name: 'customerJSON', // required name: 'customerJSON', // required
type: 'json', // required type: 'json', // required
required: true, required: true,
} },
] ],
}; }
``` ```

View File

@@ -6,42 +6,43 @@ desc: Number fields store and validate numeric data. Learn how to use and format
keywords: number, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: number, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Number field stores and validates numeric entry and supports additional numerical validation and formatting features. The Number field stores and validates numeric entry and supports additional numerical validation
and formatting features.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/number.png' srcLight="https://payloadcms.com/images/docs/fields/number.png"
srcDark='https://payloadcms.com/images/docs/fields/number-dark.png' srcDark="https://payloadcms.com/images/docs/fields/number-dark.png"
alt='Shows a Number field in the Payload admin panel' alt="Shows a Number field in the Payload admin panel"
caption='Admin panel screenshot of a Number field' caption="Admin panel screenshot of a Number field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`min`** | Minimum value accepted. Used in the default `validation` function. | | **`min`** | Minimum value accepted. Used in the default `validation` function. |
| **`max`** | Maximum value accepted. Used in the default `validation` function. | | **`max`** | Maximum value accepted. Used in the default `validation` function. |
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. | | **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. | | **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. | | **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Admin config ### Admin config
@@ -62,8 +63,9 @@ Set this property to a string that will be used for browser autocomplete.
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -74,8 +76,8 @@ export const ExampleCollection: CollectionConfig = {
required: true, required: true,
admin: { admin: {
step: 1, step: 1,
} },
} },
] ],
}; }
``` ```

View File

@@ -7,8 +7,9 @@ keywords: overview, fields, config, configuration, documentation, Content Manage
--- ---
<Banner type="info"> <Banner type="info">
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the shape of the data Fields are the building blocks of Payload. Collections and Globals both use Fields to define the
that they store. Payload offers a wide variety of field types - both simple and complex. shape of the data that they store. Payload offers a wide variety of field types - both simple and
complex.
</Banner> </Banner>
Fields are defined as an array on Collections and Globals via the `fields` key. They define the shape of the data that will be stored as well as automatically construct the corresponding Admin UI. Fields are defined as an array on Collections and Globals via the `fields` key. They define the shape of the data that will be stored as well as automatically construct the corresponding Admin UI.
@@ -16,22 +17,23 @@ Fields are defined as an array on Collections and Globals via the `fields` key.
The required `type` property on a field determines what values it can accept, how it is presented in the API, and how the field will be rendered in the admin interface. The required `type` property on a field determines what values it can accept, how it is presented in the API, and how the field will be rendered in the admin interface.
**Simple collection with two fields:** **Simple collection with two fields:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Page: CollectionConfig = { export const Page: CollectionConfig = {
slug: 'pages', slug: 'pages',
fields: [ fields: [
{ {
name: 'myField', name: 'myField',
type: 'text', // highlight-line type: 'text', // highlight-line
}, },
{ {
name: 'otherField', name: 'otherField',
type: 'checkbox', // highlight-line type: 'checkbox', // highlight-line
}, },
], ],
}; }
``` ```
### Field types ### Field types
@@ -73,11 +75,12 @@ Some fields use their `name` property as a unique identifier to store and retrie
Field validation is enforced automatically based on the field type and other properties such as `required` or `min` and `max` value constraints on certain field types. This default behavior can be replaced by providing your own validate function for any field. It will be used on both the frontend and the backend, so it should not rely on any Node-specific packages. The validation function can be either synchronous or asynchronous and expects to return either `true` or a string error message to display in both API responses and within the Admin panel. Field validation is enforced automatically based on the field type and other properties such as `required` or `min` and `max` value constraints on certain field types. This default behavior can be replaced by providing your own validate function for any field. It will be used on both the frontend and the backend, so it should not rely on any Node-specific packages. The validation function can be either synchronous or asynchronous and expects to return either `true` or a string error message to display in both API responses and within the Admin panel.
There are two arguments available to custom validation functions. There are two arguments available to custom validation functions.
1. The value which is currently assigned to the field 1. The value which is currently assigned to the field
2. An optional object with dynamic properties for more complex validation having the following: 2. An optional object with dynamic properties for more complex validation having the following:
| Property | Description | | Property | Description |
|---------------|--------------------------------------------------------------------------------------------------------------------------| | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `data` | An object of the full collection or global document. | | `data` | An object of the full collection or global document. |
| `siblingData` | An object of the document data limited to fields within the same parent to the field. | | `siblingData` | An object of the document data limited to fields within the same parent to the field. |
| `operation` | Will be "create" or "update" depending on the UI action or API call. | | `operation` | Will be "create" or "update" depending on the UI action or API call. |
@@ -87,8 +90,9 @@ There are two arguments available to custom validation functions.
| `payload` | If the `validate` function is being executed on the server, Payload will be exposed for easily running local operations. | | `payload` | If the `validate` function is being executed on the server, Payload will be exposed for easily running local operations. |
Example: Example:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Orders: CollectionConfig = { export const Orders: CollectionConfig = {
slug: 'orders', slug: 'orders',
@@ -99,38 +103,39 @@ export const Orders: CollectionConfig = {
validate: async (val, { operation }) => { validate: async (val, { operation }) => {
if (operation !== 'create') { if (operation !== 'create') {
// skip validation on update // skip validation on update
return true; return true
} }
const response = await fetch(`https://your-api.com/customers/${val}`); const response = await fetch(`https://your-api.com/customers/${val}`)
if (response.ok) { if (response.ok) {
return true; return true
} }
return 'The customer number provided does not match any customers within our records.'; return 'The customer number provided does not match any customers within our records.'
}, },
}, },
], ],
}; }
``` ```
When supplying a field `validate` function, Payload will use yours in place of the default. To make use of the default field validation in your custom logic you can import, call and return the result as needed. When supplying a field `validate` function, Payload will use yours in place of the default. To make use of the default field validation in your custom logic you can import, call and return the result as needed.
For example: For example:
```ts ```ts
import { text } from 'payload/fields/validations'; import { text } from 'payload/fields/validations'
const field: Field = { const field: Field = {
name: 'notBad', name: 'notBad',
type: 'text', type: 'text',
validate: (val, args) => { validate: (val, args) => {
if (val === 'bad') { if (val === 'bad') {
return 'This cannot be "bad"'; return 'This cannot be "bad"'
} }
// highlight-start // highlight-start
return text(val, args); return text(val, args)
// highlight-end // highlight-end
}, },
}; }
``` ```
### Customizable ID ### Customizable ID
@@ -140,6 +145,7 @@ Users are then required to provide a custom ID value when creating a record thro
Valid ID types are `number` and `text`. Valid ID types are `number` and `text`.
Example: Example:
```ts ```ts
{ {
fields: [ fields: [
@@ -155,19 +161,19 @@ Example:
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property: In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:
| Option | Description | | Option | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. | | `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. | | `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. | | `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. | | `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. | | `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. | | `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. | | `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. | | `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. | | `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. | | `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. | | `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
### Custom components ### Custom components
@@ -200,14 +206,14 @@ The `condition` function should return a boolean that will control if the field
// highlight-start // highlight-start
condition: (data, siblingData, { user }) => { condition: (data, siblingData, { user }) => {
if (data.enableGreeting) { if (data.enableGreeting) {
return true; return true
} else { } else {
return false; return false
} }
} },
// highlight-end // highlight-end
} },
} },
] ]
} }
``` ```
@@ -225,17 +231,17 @@ Here is an example of a defaultValue function that uses both:
```ts ```ts
const translation: { const translation: {
en: 'Written by', en: 'Written by'
es: 'Escrito por', es: 'Escrito por'
}; }
const field = { const field = {
name: 'attribution', name: 'attribution',
type: 'text', type: 'text',
// highlight-start // highlight-start
defaultValue: ({ user, locale }) => (`${translation[locale]} ${user.name}`) defaultValue: ({ user, locale }) => `${translation[locale]} ${user.name}`,
// highlight-end // highlight-end
}; }
``` ```
<Banner type="success"> <Banner type="success">
@@ -245,6 +251,7 @@ const field = {
### Description ### Description
A description can be configured three ways. A description can be configured three ways.
- As a string - As a string
- As a function that accepts an object containing the field's value, which returns a string - As a function that accepts an object containing the field's value, which returns a string
- As a React component that accepts value as a prop - As a React component that accepts value as a prop
@@ -262,15 +269,17 @@ As shown above, you can simply provide a string that will show by the field, but
maxLength: 20, maxLength: 20,
admin: { admin: {
description: ({ value }) => description: ({ value }) =>
(`${typeof value === 'string' ? 20 - value.length : '20'} characters left`) `${typeof value === 'string' ? 20 - value.length : '20'} characters left`,
} },
} },
] ]
} }
``` ```
This example will display the number of characters allowed as the user types. This example will display the number of characters allowed as the user types.
**Component Example:** **Component Example:**
```ts ```ts
{ {
fields: [ fields: [
@@ -292,6 +301,7 @@ This example will display the number of characters allowed as the user types.
] ]
} }
``` ```
This component will count the number of characters entered. This component will count the number of characters entered.
### TypeScript ### TypeScript
@@ -299,7 +309,5 @@ This component will count the number of characters entered.
You can import the internal Payload `Field` type as well as other common field types as follows: You can import the internal Payload `Field` type as well as other common field types as follows:
```ts ```ts
import type { import type { Field } from 'payload/types'
Field,
} from 'payload/types';
``` ```

View File

@@ -7,45 +7,47 @@ desc: The Point field type stores coordinates in the database. Learn how to use
keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Point field type saves a pair of coordinates in the database and assigns an index for location related queries. The Point field type saves a pair of coordinates in the database and assigns an index for location
related queries.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/point.png' srcLight="https://payloadcms.com/images/docs/fields/point.png"
srcDark='https://payloadcms.com/images/docs/fields/point-dark.png' srcDark="https://payloadcms.com/images/docs/fields/point-dark.png"
alt='Shows a Point field in the Payload admin panel' alt="Shows a Point field in the Payload admin panel"
caption='Admin panel screenshot of a Point field' caption="Admin panel screenshot of a Point field"
/> />
The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location. The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Used as a field label in the Admin panel and to name the generated GraphQL type. | | **`label`** | Used as a field label in the Admin panel and to name the generated GraphQL type. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -55,8 +57,8 @@ export const ExampleCollection: CollectionConfig = {
type: 'point', type: 'point',
label: 'Location', label: 'Location',
}, },
] ],
}; }
``` ```
### Querying ### Querying

View File

@@ -6,41 +6,46 @@ desc: The Radio field type allows for the selection of one value from a predefin
keywords: radio, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: radio, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Radio Group field type allows for the selection of one value from a predefined set of possible values and presents a radio group-style set of inputs to the Admin panel. The Radio Group field type allows for the selection of one value from a predefined set of possible
values and presents a radio group-style set of inputs to the Admin panel.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/radio.png' srcLight="https://payloadcms.com/images/docs/fields/radio.png"
srcDark='https://payloadcms.com/images/docs/fields/radio-dark.png' srcDark="https://payloadcms.com/images/docs/fields/radio-dark.png"
alt='Shows a Radio field in the Payload admin panel' alt="Shows a Radio field in the Payload admin panel"
caption='Admin panel screenshot of a Radio field' caption="Admin panel screenshot of a Radio field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** * | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. | | **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong><br/> <strong>Important:</strong>
Option values should be strings that do not contain hyphens or special characters due to GraphQL enumeration naming constraints. Underscores are allowed. If you determine you need your option values to be non-strings or contain special characters, they will be formatted accordingly before being used as a GraphQL enum. <br />
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
</Banner> </Banner>
### Admin config ### Admin config
@@ -54,8 +59,9 @@ The `layout` property allows for the radio group to be styled as a horizonally o
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -63,7 +69,8 @@ export const ExampleCollection: CollectionConfig = {
{ {
name: 'color', // required name: 'color', // required
type: 'radio', // required type: 'radio', // required
options: [ // required options: [
// required
{ {
label: 'Mint', label: 'Mint',
value: 'mint', value: 'mint',
@@ -76,8 +83,8 @@ export const ExampleCollection: CollectionConfig = {
defaultValue: 'mint', // The first value in options. defaultValue: 'mint', // The first value in options.
admin: { admin: {
layout: 'horizontal', layout: 'horizontal',
} },
} },
] ],
} }
``` ```

View File

@@ -7,15 +7,15 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
--- ---
<Banner> <Banner>
The Relationship field is one of the most powerful fields Payload features. It The Relationship field is one of the most powerful fields Payload features. It provides for the
provides for the ability to easily relate documents together. ability to easily relate documents together.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/relationship.png' srcLight="https://payloadcms.com/images/docs/fields/relationship.png"
srcDark='https://payloadcms.com/images/docs/fields/relationship-dark.png' srcDark="https://payloadcms.com/images/docs/fields/relationship-dark.png"
alt='Shows a relationship field in the Payload admin panel' alt="Shows a relationship field in the Payload admin panel"
caption='Admin panel screenshot of a Relationship field' caption="Admin panel screenshot of a Relationship field"
/> />
**Example uses:** **Example uses:**
@@ -54,8 +54,8 @@ _\* An asterisk denotes that a property is required._
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br /> <br />
The [Depth](/docs/getting-started/concepts#depth) parameter can be used to The [Depth](/docs/getting-started/concepts#depth) parameter can be used to automatically populate
automatically populate related documents that are returned by the API. related documents that are returned by the API.
</Banner> </Banner>
### Admin config ### Admin config
@@ -87,32 +87,32 @@ The `filterOptions` property can either be a `Where` query directly, or a functi
### Example ### Example
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "purchase", name: 'purchase',
type: "relationship", type: 'relationship',
relationTo: ["products", "services"], relationTo: ['products', 'services'],
filterOptions: ({ relationTo, siblingData }) => { filterOptions: ({ relationTo, siblingData }) => {
// returns a Where query dynamically by the type of relationship // returns a Where query dynamically by the type of relationship
if (relationTo === "products") { if (relationTo === 'products') {
return { return {
stock: { greater_than: siblingData.quantity }, stock: { greater_than: siblingData.quantity },
}; }
} }
if (relationTo === "services") { if (relationTo === 'services') {
return { return {
isAvailable: { equals: true }, isAvailable: { equals: true },
}; }
} }
}, },
}, },
], ],
}; }
``` ```
You can learn more about writing queries [here](/docs/queries/overview). You can learn more about writing queries [here](/docs/queries/overview).
@@ -120,11 +120,10 @@ You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
When a relationship field has both <strong>filterOptions</strong> and a custom{" "} When a relationship field has both <strong>filterOptions</strong> and a custom{' '}
<strong>validate</strong> function, the api will not validate{" "} <strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
<strong>filterOptions</strong> unless you call the default relationship field unless you call the default relationship field validation function imported from{' '}
validation function imported from <strong>payload/fields/validations</strong>{" "} <strong>payload/fields/validations</strong> in your validate function.
in your validate function.
</Banner> </Banner>
### How the data is saved ### How the data is saved

View File

@@ -7,20 +7,27 @@ keywords: rich text, fields, config, configuration, documentation, Content Manag
--- ---
<Banner> <Banner>
The Rich Text field is a powerful way to allow editors to write dynamic content. The content is saved as JSON in the database and can be converted into any format, including HTML, that you need. The Rich Text field is a powerful way to allow editors to write dynamic content. The content is
saved as JSON in the database and can be converted into any format, including HTML, that you need.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/richtext.png' srcLight="https://payloadcms.com/images/docs/fields/richtext.png"
srcDark='https://payloadcms.com/images/docs/fields/richtext-dark.png' srcDark="https://payloadcms.com/images/docs/fields/richtext-dark.png"
alt='Shows a Rich Text field in the Payload admin panel' alt="Shows a Rich Text field in the Payload admin panel"
caption='Admin panel screenshot of a Rich Text field' caption="Admin panel screenshot of a Rich Text field"
/> />
The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.org/) editor and is meant to be as extensible and customizable as possible. The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.org/) editor and is meant to be as extensible and customizable as possible.
<Banner type="success"> <Banner type="success">
<strong>Consistent with Payload's goal of making you learn as little of Payload as possible, customizing and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em> rich text editor.</strong> Instead, you can invest your time and effort into learning Slate, an open-source tool that will allow you to apply your learnings elsewhere as well. <strong>
Consistent with Payload's goal of making you learn as little of Payload as possible, customizing
and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em>{' '}
rich text editor.
</strong>{' '}
Instead, you can invest your time and effort into learning Slate, an open-source tool that will
allow you to apply your learnings elsewhere as well.
</Banner> </Banner>
### Config ### Config
@@ -120,7 +127,12 @@ The built-in `relationship` element is a powerful way to reference other Documen
Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads. Similar to the `relationship` element, the `upload` element is a user-friendly way to reference [Upload-enabled collections](/docs/upload/overview) with a UI specifically designed for media / image-based uploads.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br />Collections are automatically allowed to be selected within the Rich Text relationship and upload elements by default. If you want to disable a collection from being able to be referenced in Rich Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and <strong>enableRichTextRelationship</strong> to false. <strong>Tip:</strong>
<br />
Collections are automatically allowed to be selected within the Rich Text relationship and upload
elements by default. If you want to disable a collection from being able to be referenced in Rich
Text fields, set the collection admin options of <strong>enableRichTextLink</strong> and{' '}
<strong>enableRichTextRelationship</strong> to false.
</Banner> </Banner>
Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize. Relationship and Upload elements are populated dynamically into your Rich Text field' content. Within the REST and Local APIs, any present RichText `relationship` or `upload` elements will respect the `depth` option that you pass, and will be populated accordingly. In GraphQL, each `richText` field accepts an argument of `depth` for you to utilize.
@@ -159,29 +171,29 @@ Specifying custom `Type`s let you extend your custom elements by adding addition
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "content", // required name: 'content', // required
type: "richText", // required type: 'richText', // required
defaultValue: [ defaultValue: [
{ {
children: [{ text: "Here is some default content for this field" }], children: [{ text: 'Here is some default content for this field' }],
}, },
], ],
required: true, required: true,
admin: { admin: {
elements: [ elements: [
"h2", 'h2',
"h3", 'h3',
"h4", 'h4',
"link", 'link',
"blockquote", 'blockquote',
{ {
name: "cta", name: 'cta',
Button: CustomCallToActionButton, Button: CustomCallToActionButton,
Element: CustomCallToActionElement, Element: CustomCallToActionElement,
plugins: [ plugins: [
@@ -190,10 +202,10 @@ export const ExampleCollection: CollectionConfig = {
}, },
], ],
leaves: [ leaves: [
"bold", 'bold',
"italic", 'italic',
{ {
name: "highlight", name: 'highlight',
Button: CustomHighlightButton, Button: CustomHighlightButton,
Leaf: CustomHighlightLeaf, Leaf: CustomHighlightLeaf,
plugins: [ plugins: [
@@ -205,11 +217,11 @@ export const ExampleCollection: CollectionConfig = {
// Inject your own fields into the Link element // Inject your own fields into the Link element
fields: [ fields: [
{ {
name: "rel", name: 'rel',
label: "Rel Attribute", label: 'Rel Attribute',
type: "select", type: 'select',
hasMany: true, hasMany: true,
options: ["noopener", "noreferrer", "nofollow"], options: ['noopener', 'noreferrer', 'nofollow'],
}, },
], ],
}, },
@@ -226,7 +238,7 @@ export const ExampleCollection: CollectionConfig = {
}, },
}, },
], ],
}; }
``` ```
For more examples regarding how to define your own elements and leaves, check out the example [`RichText` field](https://github.com/payloadcms/public-demo/blob/master/src/fields/hero.ts) within the Public Demo source code. For more examples regarding how to define your own elements and leaves, check out the example [`RichText` field](https://github.com/payloadcms/public-demo/blob/master/src/fields/hero.ts) within the Public Demo source code.
@@ -296,7 +308,10 @@ const serialize = (children) =>
``` ```
<Banner> <Banner>
<strong>Note:</strong><br />The above example is for how to render to JSX, although for plain HTML the pattern is similar. Just remove the JSX and return HTML strings instead! <strong>Note:</strong>
<br />
The above example is for how to render to JSX, although for plain HTML the pattern is similar.
Just remove the JSX and return HTML strings instead!
</Banner> </Banner>
### Built-in SlateJS Plugins ### Built-in SlateJS Plugins
@@ -312,26 +327,26 @@ If you want to utilize this functionality within your own custom elements, you c
`customLargeBodyElement.js`: `customLargeBodyElement.js`:
```ts ```ts
import Button from "./Button"; import Button from './Button'
import Element from "./Element"; import Element from './Element'
import withLargeBody from "./plugin"; import withLargeBody from './plugin'
export default { export default {
name: "large-body", name: 'large-body',
Button, Button,
Element, Element,
plugins: [ plugins: [
(incomingEditor) => { (incomingEditor) => {
const editor = incomingEditor; const editor = incomingEditor
const { shouldBreakOutOnEnter } = editor; const { shouldBreakOutOnEnter } = editor
editor.shouldBreakOutOnEnter = (element) => editor.shouldBreakOutOnEnter = (element) =>
element.type === "large-body" ? true : shouldBreakOutOnEnter(element); element.type === 'large-body' ? true : shouldBreakOutOnEnter(element)
return editor; return editor
}, },
], ],
}; }
``` ```
Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin. Above, you can see that we are creating a custom SlateJS element with a name of `large-body`. This might render a slightly larger body copy on the frontend of your app(s). We pass it a name, button, and element&mdash;but additionally, we pass it a `plugins` array containing a single SlateJS plugin.
@@ -343,5 +358,5 @@ The plugin itself extends Payload's built-in `shouldBreakOutOnEnter` Slate funct
If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types: If you are building your own custom Rich Text elements or leaves, you may benefit from importing the following types:
```ts ```ts
import type { RichTextCustomElement, RichTextCustomLeaf } from "payload/types"; import type { RichTextCustomElement, RichTextCustomLeaf } from 'payload/types'
``` ```

View File

@@ -6,39 +6,42 @@ desc: With the Row field you can arrange fields next to each other in the Admin
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Row field is presentational-only and only affects the Admin panel. By using it, you can arrange fields next to each other horizontally. The Row field is presentational-only and only affects the Admin panel. By using it, you can
arrange fields next to each other horizontally.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/row.png' srcLight="https://payloadcms.com/images/docs/fields/row.png"
srcDark='https://payloadcms.com/images/docs/fields/row-dark.png' srcDark="https://payloadcms.com/images/docs/fields/row-dark.png"
alt='Shows a row field in the Payload admin panel' alt="Shows a row field in the Payload admin panel"
caption='Admin panel screenshot of a Row field' caption="Admin panel screenshot of a Row field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`fields`** * | Array of field types to nest within this Row. | | **`fields`** \* | Array of field types to nest within this Row. |
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. | | **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
fields: [ fields: [
{ {
type: 'row', // required type: 'row', // required
fields: [ // required fields: [
// required
{ {
name: 'label', name: 'label',
type: 'text', type: 'text',
@@ -56,8 +59,7 @@ export const ExampleCollection: CollectionConfig = {
}, },
}, },
], ],
} },
] ],
} }
``` ```

View File

@@ -7,21 +7,21 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
--- ---
<Banner> <Banner>
The Select field provides a dropdown-style interface for choosing options from The Select field provides a dropdown-style interface for choosing options from a predefined list
a predefined list as an enumeration. as an enumeration.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/select.png' srcLight="https://payloadcms.com/images/docs/fields/select.png"
srcDark='https://payloadcms.com/images/docs/fields/select-dark.png' srcDark="https://payloadcms.com/images/docs/fields/select-dark.png"
alt='Shows a Select field in the Payload admin panel' alt="Shows a Select field in the Payload admin panel"
caption='Admin panel screenshot of a Select field' caption="Admin panel screenshot of a Select field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ------------------ |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. | | **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. | | **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
@@ -44,11 +44,10 @@ _\* An asterisk denotes that a property is required._
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
Option values should be strings that do not contain hyphens or special Option values should be strings that do not contain hyphens or special characters due to GraphQL
characters due to GraphQL enumeration naming constraints. Underscores are enumeration naming constraints. Underscores are allowed. If you determine you need your option
allowed. If you determine you need your option values to be non-strings or values to be non-strings or contain special characters, they will be formatted accordingly before
contain special characters, they will be formatted accordingly before being being used as a GraphQL enum.
used as a GraphQL enum.
</Banner> </Banner>
### Admin config ### Admin config
@@ -66,8 +65,9 @@ Set to `true` if you'd like this field to be sortable within the Admin UI using
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -94,8 +94,7 @@ export const ExampleCollection: CollectionConfig = {
value: 'carbon_fiber_dashboard', value: 'carbon_fiber_dashboard',
}, },
], ],
} },
] ],
} }
``` ```

View File

@@ -7,19 +7,18 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
--- ---
<Banner> <Banner>
The Tabs field is presentational-only and only affects the Admin panel (unless The Tabs field is presentational-only and only affects the Admin panel (unless a tab is named). By
a tab is named). By using it, you can place fields within a nice layout using it, you can place fields within a nice layout component that separates certain sub-fields by
component that separates certain sub-fields by a tabbed interface. a tabbed interface.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/tabs.png' srcLight="https://payloadcms.com/images/docs/fields/tabs.png"
srcDark='https://payloadcms.com/images/docs/fields/tabs-dark.png' srcDark="https://payloadcms.com/images/docs/fields/tabs-dark.png"
alt='Shows a tabs field used to separate Hero and Page layout in the Payload admin panel' alt="Shows a tabs field used to separate Hero and Page layout in the Payload admin panel"
caption='Tabs field type used to separate Hero fields from Page Layout' caption="Tabs field type used to separate Hero fields from Page Layout"
/> />
### Config ### Config
| Option | Description | | Option | Description |
@@ -32,12 +31,12 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
Each tab must have either a `name` or `label` and the required `fields` array. You can also optionally pass a `description` to render within each individual tab. Each tab must have either a `name` or `label` and the required `fields` array. You can also optionally pass a `description` to render within each individual tab.
| Option | Description | | Option | Description |
| ---------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. | | **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. | | **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. | | **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) | | **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
_\* An asterisk denotes that a property is required._ _\* An asterisk denotes that a property is required._
@@ -47,36 +46,36 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
type: "tabs", // required type: 'tabs', // required
tabs: [ tabs: [
// required // required
{ {
label: "Tab One Label", // required label: 'Tab One Label', // required
description: "This will appear within the tab above the fields.", description: 'This will appear within the tab above the fields.',
fields: [ fields: [
// required // required
{ {
name: "someTextField", name: 'someTextField',
type: "text", type: 'text',
required: true, required: true,
}, },
], ],
}, },
{ {
name: "tabTwo", name: 'tabTwo',
label: "Tab Two Label", // required label: 'Tab Two Label', // required
interfaceName: "TabTwo", // optional (`name` must be present) interfaceName: 'TabTwo', // optional (`name` must be present)
fields: [ fields: [
// required // required
{ {
name: "numberField", // accessible via tabTwo.numberField name: 'numberField', // accessible via tabTwo.numberField
type: "number", type: 'number',
required: true, required: true,
}, },
], ],
@@ -84,5 +83,5 @@ export const ExampleCollection: CollectionConfig = {
], ],
}, },
], ],
}; }
``` ```

View File

@@ -6,39 +6,40 @@ desc: Text field types simply save a string to the database and provide the Admi
keywords: text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Text field type is one of the most commonly used fields. It saves a string to the database and provides the Admin panel with a simple text input. The Text field type is one of the most commonly used fields. It saves a string to the database and
provides the Admin panel with a simple text input.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/text.png' srcLight="https://payloadcms.com/images/docs/fields/text.png"
srcDark='https://payloadcms.com/images/docs/fields/text-dark.png' srcDark="https://payloadcms.com/images/docs/fields/text-dark.png"
alt='Shows a text field and read-only text field in the Payload admin panel' alt="Shows a text field and read-only text field in the Payload admin panel"
caption='Admin panel screenshot of a Text field and read-only Text field' caption="Admin panel screenshot of a Text field and read-only Text field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | | **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | | **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Admin config ### Admin config
@@ -59,8 +60,9 @@ Override the default text direction of the Admin panel for this field. Set to `t
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -69,8 +71,7 @@ export const ExampleCollection: CollectionConfig = {
name: 'pageTitle', // required name: 'pageTitle', // required
type: 'text', // required type: 'text', // required
required: true, required: true,
} },
] ],
} }
``` ```

View File

@@ -6,39 +6,40 @@ desc: Textarea field types save a string to the database, similar to the Text fi
keywords: textarea, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: textarea, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner > <Banner>
The Textarea field is almost identical to the Text field but it features a slightly larger input that is better suited to edit longer text. The Textarea field is almost identical to the Text field but it features a slightly larger input
that is better suited to edit longer text.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/textarea.png' srcLight="https://payloadcms.com/images/docs/fields/textarea.png"
srcDark='https://payloadcms.com/images/docs/fields/textarea-dark.png' srcDark="https://payloadcms.com/images/docs/fields/textarea-dark.png"
alt='Shows a textarea field and read-only textarea field in the Payload admin panel' alt="Shows a textarea field and read-only textarea field in the Payload admin panel"
caption='Admin panel screenshot of a Textarea field and read-only Textarea field' caption="Admin panel screenshot of a Textarea field and read-only Textarea field"
/> />
### Config ### Config
| Option | Description | | Option | Description |
| ---------------- | ----------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | | **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | | **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. | | **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. | | **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | | **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values)| | **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | | **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. | | **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). | | **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`custom`** | Extension point for adding custom data (e.g. for plugins) |
*\* An asterisk denotes that a property is required.* _\* An asterisk denotes that a property is required._
### Admin config ### Admin config
@@ -59,8 +60,9 @@ Override the default text direction of the Admin panel for this field. Set to `t
### Example ### Example
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: 'example-collection', slug: 'example-collection',
@@ -69,8 +71,7 @@ export const ExampleCollection: CollectionConfig = {
name: 'metaDescription', // required name: 'metaDescription', // required
type: 'textarea', // required type: 'textarea', // required
required: true, required: true,
} },
] ],
} }
``` ```

View File

@@ -7,10 +7,9 @@ keywords: custom field, react component, fields, config, configuration, document
--- ---
<Banner> <Banner>
The UI (user interface) field gives you a ton of power to add your own React The UI (user interface) field gives you a ton of power to add your own React components directly
components directly into the Admin panel, nested directly within your other into the Admin panel, nested directly within your other fields. It has absolutely no effect on the
fields. It has absolutely no effect on the data of your documents. It is data of your documents. It is presentational-only.
presentational-only.
</Banner> </Banner>
This field is helpful if you need to build in custom functionality via React components within the Admin panel. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go. This field is helpful if you need to build in custom functionality via React components within the Admin panel. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
@@ -41,14 +40,14 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "myCustomUIField", // required name: 'myCustomUIField', // required
type: "ui", // required type: 'ui', // required
admin: { admin: {
components: { components: {
Field: MyCustomUIField, Field: MyCustomUIField,
@@ -57,5 +56,5 @@ export const ExampleCollection: CollectionConfig = {
}, },
}, },
], ],
}; }
``` ```

View File

@@ -7,19 +7,23 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
--- ---
<Banner> <Banner>
The Upload field allows for the selection of a Document from a collection supporting Uploads, and formats the selection as a thumbnail in the Admin panel. The Upload field allows for the selection of a Document from a collection supporting Uploads, and
formats the selection as a thumbnail in the Admin panel.
</Banner> </Banner>
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong><br /> <strong>Important:</strong>
To use this field, you need to have a Collection configured to allow Uploads. For more information, [click here](/docs/upload/overview) to read about how to enable Uploads on a collection by collection basis. <br />
To use this field, you need to have a Collection configured to allow Uploads. For more
information, [click here](/docs/upload/overview) to read about how to enable Uploads on a
collection by collection basis.
</Banner> </Banner>
<LightDarkImage <LightDarkImage
srcLight='https://payloadcms.com/images/docs/fields/upload.png' srcLight="https://payloadcms.com/images/docs/fields/upload.png"
srcDark='https://payloadcms.com/images/docs/fields/upload-dark.png' srcDark="https://payloadcms.com/images/docs/fields/upload-dark.png"
alt='Shows an upload field in the Payload admin panel' alt="Shows an upload field in the Payload admin panel"
caption='Admin panel screenshot of an Upload field' caption="Admin panel screenshot of an Upload field"
/> />
**Example uses:** **Example uses:**
@@ -57,19 +61,19 @@ _\* An asterisk denotes that a property is required._
`collections/ExampleCollection.ts` `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = { export const ExampleCollection: CollectionConfig = {
slug: "example-collection", slug: 'example-collection',
fields: [ fields: [
{ {
name: "backgroundImage", // required name: 'backgroundImage', // required
type: "upload", // required type: 'upload', // required
relationTo: "media", // required relationTo: 'media', // required
required: true, required: true,
}, },
], ],
}; }
``` ```
### Filtering upload options ### Filtering upload options
@@ -90,18 +94,22 @@ The `filterOptions` property can either be a `Where` query directly, or a functi
```ts ```ts
const uploadField = { const uploadField = {
name: "image", name: 'image',
type: "upload", type: 'upload',
relationTo: "media", relationTo: 'media',
filterOptions: { filterOptions: {
mimeType: { contains: "image" }, mimeType: { contains: 'image' },
}, },
}; }
``` ```
You can learn more about writing queries [here](/docs/queries/overview). You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong><br /> <strong>Note:</strong>
When an upload field has both <strong>filterOptions</strong> and a custom <strong>validate</strong> function, the api will not validate <strong>filterOptions</strong> unless you call the default upload field validation function imported from <strong>payload/fields/validations</strong> in your validate function. <br />
When an upload field has both <strong>filterOptions</strong> and a custom{' '}
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
unless you call the default upload field validation function imported from{' '}
<strong>payload/fields/validations</strong> in your validate function.
</Banner> </Banner>

View File

@@ -10,16 +10,14 @@ Payload is based around a small and intuitive set of concepts. Before starting t
### Config ### Config
<Banner type="info"> <Banner type="info">The Payload config is where you configure everything that Payload does.</Banner>
The Payload config is where you configure everything that Payload does.
</Banner>
By default, the Payload config lives in the root folder of your code and is named `payload.config.js` (`payload.config.ts` if you're using TypeScript), but you can customize its name and where you store it. You can write full functions and even full React components right into your config. By default, the Payload config lives in the root folder of your code and is named `payload.config.js` (`payload.config.ts` if you're using TypeScript), but you can customize its name and where you store it. You can write full functions and even full React components right into your config.
### Collections ### Collections
<Banner type="info"> <Banner type="info">
A Collection represents a type of content that Payload will store and can contain many documents. A Collection represents a type of content that Payload will store and can contain many documents.
</Banner> </Banner>
Collections define the shape of your data as well as all functionalities attached to that data. They will contain one or many "documents", all corresponding with the same fields and functionalities that you define. Collections define the shape of your data as well as all functionalities attached to that data. They will contain one or many "documents", all corresponding with the same fields and functionalities that you define.
@@ -29,7 +27,8 @@ They can represent anything you can store in a database - for example - pages, p
### Globals ### Globals
<Banner type="info"> <Banner type="info">
A Global is a "one-off" piece of content that is perfect for storing navigational structures, themes, top-level meta data, and more. A Global is a "one-off" piece of content that is perfect for storing navigational structures,
themes, top-level meta data, and more.
</Banner> </Banner>
Globals are in many ways similar to Collections, but there is only ever **one** instance of a Global, whereas Collections can contain many documents. Globals are in many ways similar to Collections, but there is only ever **one** instance of a Global, whereas Collections can contain many documents.
@@ -37,7 +36,8 @@ Globals are in many ways similar to Collections, but there is only ever **one**
### Fields ### Fields
<Banner type="info"> <Banner type="info">
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the shape of the data that they store. Fields are the building blocks of Payload. Collections and Globals both use Fields to define the
shape of the data that they store.
</Banner> </Banner>
Payload comes with [many different field types](../fields/overview) that give you a ton of flexibility while designing your API. Each Field type has its own potential properties that allow you to customize how they work. Payload comes with [many different field types](../fields/overview) that give you a ton of flexibility while designing your API. Each Field type has its own potential properties that allow you to customize how they work.
@@ -45,7 +45,8 @@ Payload comes with [many different field types](../fields/overview) that give yo
### Hooks ### Hooks
<Banner type="info"> <Banner type="info">
Hooks are where you can "tie in" to existing Payload actions to perform your own additional logic or modify how Payload operates altogether. Hooks are where you can "tie in" to existing Payload actions to perform your own additional logic
or modify how Payload operates altogether.
</Banner> </Banner>
Hooks are an extremely powerful concept and are central to extending and customizing your app. Payload provides a wide variety of hooks which you can utilize. For example, imagine if you'd like to send an email every time a document is created in your Orders collection. To do so, you can add an `afterChange` hook function to your Orders collection that receives the Order data and allows you to send an email accordingly. Hooks are an extremely powerful concept and are central to extending and customizing your app. Payload provides a wide variety of hooks which you can utilize. For example, imagine if you'd like to send an email every time a document is created in your Orders collection. To do so, you can add an `afterChange` hook function to your Orders collection that receives the Order data and allows you to send an email accordingly.
@@ -65,10 +66,11 @@ For more, visit the [Access Control documentation](/docs/access-control/overview
### Depth ### Depth
<Banner type="info"> <Banner type="info">
"Depth" gives you control over how many levels down related documents should be automatically populated when retrieved. "Depth" gives you control over how many levels down related documents should be automatically
populated when retrieved.
</Banner> </Banner>
You can specify population `depth` via query parameter in the REST API and by an option in the local API. *Depth has no effect in the GraphQL API, because there, depth is based on the shape of your queries.* You can specify population `depth` via query parameter in the REST API and by an option in the local API. _Depth has no effect in the GraphQL API, because there, depth is based on the shape of your queries._
It is also possible to limit the depth for specific `relation` and `upload` fields using the `maxDepth` property in your configuration. It is also possible to limit the depth for specific `relation` and `upload` fields using the `maxDepth` property in your configuration.
**For example, let's look at the following Collections:** `departments`, `users`, `posts` **For example, let's look at the following Collections:** `departments`, `users`, `posts`
@@ -155,6 +157,8 @@ To populate `user.author.department` in it's entirety you could specify `?depth=
``` ```
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong><br/> <strong>Note:</strong>
When access control on collections prevents relationship fields from populating, the API response will contain the relationship id instead of the full document. <br />
When access control on collections prevents relationship fields from populating, the API response
will contain the relationship id instead of the full document.
</Banner> </Banner>

View File

@@ -15,8 +15,7 @@ Payload requires the following software:
- A MongoDB Database - A MongoDB Database
<Banner type="warning"> <Banner type="warning">
Before proceeding any further, please ensure that you have the above Before proceeding any further, please ensure that you have the above requirements met.
requirements met.
</Banner> </Banner>
## Quickstart with create-payload-app ## Quickstart with create-payload-app
@@ -36,13 +35,13 @@ Adding Payload to either a new or existing TypeScript + Express app is super str
From there, the first step is writing a baseline config. Create a new `payload.config.ts` in your project's `/src` directory (or whatever your root TS dir is). The simplest config contains the following: From there, the first step is writing a baseline config. Create a new `payload.config.ts` in your project's `/src` directory (or whatever your root TS dir is). The simplest config contains the following:
```js ```js
import { buildConfig } from "payload/config"; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
// By default, Payload will boot up normally // By default, Payload will boot up normally
// and you will be provided with a base `User` collection. // and you will be provided with a base `User` collection.
// But, here is where you define how you'd like Payload to work! // But, here is where you define how you'd like Payload to work!
}); })
``` ```
Write the above code into your newly created config file. This baseline config will automatically provide you with a default `User` collection. For more information about users and authentication, including how to provide your own user config, jump to the [Authentication](/docs/authentication/config) section. Write the above code into your newly created config file. This baseline config will automatically provide you with a default `User` collection. For more information about users and authentication, including how to provide your own user config, jump to the [Authentication](/docs/authentication/config) section.
@@ -58,15 +57,13 @@ Now that you've got a baseline Payload config, it's time to initialize Payload.
1. Add the following code to `server.ts`: 1. Add the following code to `server.ts`:
```ts ```ts
import express from "express"; import express from 'express'
const app = express(); const app = express()
app.listen(3000, async () => { app.listen(3000, async () => {
console.log( console.log('Express is now listening for incoming connections on port 3000.')
"Express is now listening for incoming connections on port 3000." })
);
});
``` ```
This server doesn't do anything just yet. But, after you have this in place, we can initialize Payload via its asynchronous `init()` method, which accepts a small set of arguments to tell it how to operate. This server doesn't do anything just yet. But, after you have this in place, we can initialize Payload via its asynchronous `init()` method, which accepts a small set of arguments to tell it how to operate.
@@ -74,27 +71,25 @@ This server doesn't do anything just yet. But, after you have this in place, we
To initialize Payload, update your `server.ts` file to reflect the following code: To initialize Payload, update your `server.ts` file to reflect the following code:
```ts ```ts
import express from "express"; import express from 'express'
import payload from "payload"; import payload from 'payload'
require("dotenv").config(); require('dotenv').config()
const app = express(); const app = express()
const start = async () => { const start = async () => {
await payload.init({ await payload.init({
secret: process.env.PAYLOAD_SECRET, secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI, mongoURL: process.env.MONGODB_URI,
express: app, express: app,
}); })
app.listen(3000, async () => { app.listen(3000, async () => {
console.log( console.log('Express is now listening for incoming connections on port 3000.')
"Express is now listening for incoming connections on port 3000." })
); }
});
};
start(); start()
``` ```
A quick reminder: in this configuration, we're making use of two environmental variables, `process.env.PAYLOAD_SECRET` and `process.env.MONGODB_URI`. Often, it's smart to store these values in an `.env` file at the root of your directory and set different values for each of your environments (local, stage, prod, etc). The `dotenv` package is very handy and works well alongside of Payload. A typical `.env` file will look like this: A quick reminder: in this configuration, we're making use of two environmental variables, `process.env.PAYLOAD_SECRET` and `process.env.MONGODB_URI`. Often, it's smart to store these values in an `.env` file at the root of your directory and set different values for each of your environments (local, stage, prod, etc). The `dotenv` package is very handy and works well alongside of Payload. A typical `.env` file will look like this:

View File

@@ -12,9 +12,8 @@ keywords: documentation, getting started, guide, Content Management System, cms,
/> />
<Banner type="success"> <Banner type="success">
Payload is a headless CMS and application framework. Its meant to provide a Payload is a headless CMS and application framework. Its meant to provide a massive boost to your
massive boost to your development process, but importantly, stay out of your development process, but importantly, stay out of your way as your apps get more complex.
way as your apps get more complex.
</Banner> </Banner>
Out of the box, Payload gives you a lot of the things that you often need when developing a new website, web app, or native app: Out of the box, Payload gives you a lot of the things that you often need when developing a new website, web app, or native app:

View File

@@ -10,10 +10,10 @@ You can add your own GraphQL queries and mutations to Payload, making use of all
To do so, add your queries and mutations to the main Payload config as follows: To do so, add your queries and mutations to the main Payload config as follows:
| Config Path | Description | | Config Path | Description |
| -------------------- | -------------| | ------------------- | --------------------------------------------------------------------------- |
| `graphQL.queries` | Function that returns an object containing keys to custom GraphQL queries | | `graphQL.queries` | Function that returns an object containing keys to custom GraphQL queries |
| `graphQL.mutations` | Function that returns an object containing keys to custom GraphQL mutations | | `graphQL.mutations` | Function that returns an object containing keys to custom GraphQL mutations |
The above properties each receive a function that is defined with the following arguments: The above properties each receive a function that is defined with the following arguments:
@@ -34,8 +34,8 @@ Both `graphQL.queries` and `graphQL.mutations` functions should return an object
`payload.config.js`: `payload.config.js`:
```ts ```ts
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver'; import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver'
export default buildConfig({ export default buildConfig({
graphQL: { graphQL: {
@@ -57,14 +57,14 @@ export default buildConfig({
args: { args: {
argNameHere: { argNameHere: {
type: new GraphQL.GraphQLNonNull(GraphQLString), type: new GraphQL.GraphQLNonNull(GraphQLString),
} },
}, },
resolve: myCustomQueryResolver, resolve: myCustomQueryResolver,
} },
} }
} },
// highlight-end // highlight-end
} },
}) })
``` ```
@@ -77,10 +77,10 @@ Your function will receive four arguments you can make use of:
Example Example
```ts ```ts
async (obj, args, context, info) => { } ;async (obj, args, context, info) => {}
``` ```
**`obj`** **`obj`**
The previous object. Not very often used and usually discarded. The previous object. Not very often used and usually discarded.

View File

@@ -102,8 +102,13 @@ GraphQL Playground is enabled by default for development purposes, but disabled
You can even log in using the `login[collection-singular-label-here]` mutation to use the Playground as an authenticated user. You can even log in using the `login[collection-singular-label-here]` mutation to use the Playground as an authenticated user.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br /> <strong>Tip:</strong>
To see more regarding how the above queries and mutations are used, visit your GraphQL playground (by default at [http://localhost:3000/api/graphql-playground](http://localhost:3000/api/graphql-playground)) while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to see a ton of detail about how GraphQL operates within Payload. <br />
To see more regarding how the above queries and mutations are used, visit your GraphQL playground
(by default at
[http://localhost:3000/api/graphql-playground](http://localhost:3000/api/graphql-playground))
while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to
see a ton of detail about how GraphQL operates within Payload.
</Banner> </Banner>
## Query complexity limits ## Query complexity limits

View File

@@ -70,14 +70,14 @@ The `beforeOperation` hook can be used to modify the arguments that operations a
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`. Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`.
```ts ```ts
import { CollectionBeforeOperationHook } from "payload/types"; import { CollectionBeforeOperationHook } from 'payload/types'
const beforeOperationHook: CollectionBeforeOperationHook = async ({ const beforeOperationHook: CollectionBeforeOperationHook = async ({
args, // original arguments passed into the operation args, // original arguments passed into the operation
operation, // name of the operation operation, // name of the operation
}) => { }) => {
return args; // return modified operation arguments as necessary return args // return modified operation arguments as necessary
}; }
``` ```
### beforeValidate ### beforeValidate
@@ -91,7 +91,7 @@ Please do note that this does not run before the client-side validation. If you
3. `validate` runs on the server 3. `validate` runs on the server
```ts ```ts
import { CollectionBeforeOperationHook } from "payload/types"; import { CollectionBeforeOperationHook } from 'payload/types'
const beforeValidateHook: CollectionBeforeValidateHook = async ({ const beforeValidateHook: CollectionBeforeValidateHook = async ({
data, // incoming data to update or create with data, // incoming data to update or create with
@@ -99,8 +99,8 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
operation, // name of the operation ie. 'create', 'update' operation, // name of the operation ie. 'create', 'update'
originalDoc, // original document originalDoc, // original document
}) => { }) => {
return data; // Return data to either create or update a document with return data // Return data to either create or update a document with
}; }
``` ```
### beforeChange ### beforeChange
@@ -108,7 +108,7 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved. Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
```ts ```ts
import { CollectionBeforeChangeHook } from "payload/types"; import { CollectionBeforeChangeHook } from 'payload/types'
const beforeChangeHook: CollectionBeforeChangeHook = async ({ const beforeChangeHook: CollectionBeforeChangeHook = async ({
data, // incoming data to update or create with data, // incoming data to update or create with
@@ -116,8 +116,8 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
operation, // name of the operation ie. 'create', 'update' operation, // name of the operation ie. 'create', 'update'
originalDoc, // original document originalDoc, // original document
}) => { }) => {
return data; // Return data to either create or update a document with return data // Return data to either create or update a document with
}; }
``` ```
### afterChange ### afterChange
@@ -125,7 +125,7 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more. After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more.
```ts ```ts
import { CollectionAfterChangeHook } from "payload/types"; import { CollectionAfterChangeHook } from 'payload/types'
const afterChangeHook: CollectionAfterChangeHook = async ({ const afterChangeHook: CollectionAfterChangeHook = async ({
doc, // full document data doc, // full document data
@@ -133,8 +133,8 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
previousDoc, // document data before updating the collection previousDoc, // document data before updating the collection
operation, // name of the operation ie. 'create', 'update' operation, // name of the operation ie. 'create', 'update'
}) => { }) => {
return doc; return doc
}; }
``` ```
### beforeRead ### beforeRead
@@ -142,15 +142,15 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument. Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
```ts ```ts
import { CollectionBeforeReadHook } from "payload/types"; import { CollectionBeforeReadHook } from 'payload/types'
const beforeReadHook: CollectionBeforeReadHook = async ({ const beforeReadHook: CollectionBeforeReadHook = async ({
doc, // full document data doc, // full document data
req, // full express request req, // full express request
query, // JSON formatted query query, // JSON formatted query
}) => { }) => {
return doc; return doc
}; }
``` ```
### afterRead ### afterRead
@@ -158,7 +158,7 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to. Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
```ts ```ts
import { CollectionAfterReadHook } from "payload/types"; import { CollectionAfterReadHook } from 'payload/types'
const afterReadHook: CollectionAfterReadHook = async ({ const afterReadHook: CollectionAfterReadHook = async ({
doc, // full document data doc, // full document data
@@ -166,8 +166,8 @@ const afterReadHook: CollectionAfterReadHook = async ({
query, // JSON formatted query query, // JSON formatted query
findMany, // boolean to denote if this hook is running against finding one, or finding many findMany, // boolean to denote if this hook is running against finding one, or finding many
}) => { }) => {
return doc; return doc
}; }
``` ```
### beforeDelete ### beforeDelete
@@ -204,15 +204,15 @@ The `afterOperation` hook can be used to modify the result of operations or exec
Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`. Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`.
```ts ```ts
import { CollectionAfterOperationHook } from "payload/types"; import { CollectionAfterOperationHook } from 'payload/types'
const afterOperationHook: CollectionAfterOperationHook = async ({ const afterOperationHook: CollectionAfterOperationHook = async ({
args, // arguments passed into the operation args, // arguments passed into the operation
operation, // name of the operation operation, // name of the operation
result, // the result of the operation, before modifications result, // the result of the operation, before modifications
}) => { }) => {
return result; // return modified result as necessary return result // return modified result as necessary
}; }
``` ```
### beforeLogin ### beforeLogin
@@ -220,14 +220,14 @@ const afterOperationHook: CollectionAfterOperationHook = async ({
For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation. For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation.
```ts ```ts
import { CollectionBeforeLoginHook } from "payload/types"; import { CollectionBeforeLoginHook } from 'payload/types'
const beforeLoginHook: CollectionBeforeLoginHook = async ({ const beforeLoginHook: CollectionBeforeLoginHook = async ({
req, // full express request req, // full express request
user, // user being logged in user, // user being logged in
}) => { }) => {
return user; return user
}; }
``` ```
### afterLogin ### afterLogin
@@ -288,15 +288,15 @@ const afterMeHook: CollectionAfterMeHook = async ({
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded. For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
```ts ```ts
import { CollectionAfterForgotPasswordHook } from "payload/types"; import { CollectionAfterForgotPasswordHook } from 'payload/types'
const afterLoginHook: CollectionAfterForgotPasswordHook = async ({ const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
req, // full express request req, // full express request
user, // user being logged in user, // user being logged in
token, // user token token, // user token
}) => { }) => {
return user; return user
}; }
``` ```
## TypeScript ## TypeScript
@@ -319,5 +319,5 @@ import type {
CollectionAfterRefreshHook, CollectionAfterRefreshHook,
CollectionAfterMeHook, CollectionAfterMeHook,
CollectionAfterForgotPasswordHook, CollectionAfterForgotPasswordHook,
} from "payload/types"; } from 'payload/types'
``` ```

View File

@@ -31,24 +31,30 @@ For example:
const Customer: CollectionConfig = { const Customer: CollectionConfig = {
slug: 'customers', slug: 'customers',
hooks: { hooks: {
beforeChange: [async ({ context, data }) => { beforeChange: [
// assign the customerData to context for use later async ({ context, data }) => {
context.customerData = await fetchCustomerData(data.customerID); // assign the customerData to context for use later
return { context.customerData = await fetchCustomerData(data.customerID)
...data, return {
// some data we use here ...data,
name: context.customerData.name // some data we use here
}; name: context.customerData.name,
}], }
afterChange: [async ({ context, doc, req }) => { },
// use context.customerData without needing to fetch it again ],
if (context.customerData.contacted === false) { afterChange: [
createTodo('Call Customer', context.customerData) async ({ context, doc, req }) => {
} // use context.customerData without needing to fetch it again
}], if (context.customerData.contacted === false) {
createTodo('Call Customer', context.customerData)
}
},
],
}, },
fields: [ /* ... */ ], fields: [
}; /* ... */
],
}
``` ```
### Preventing infinite loops ### Preventing infinite loops
@@ -61,19 +67,23 @@ Bad example:
const Customer: CollectionConfig = { const Customer: CollectionConfig = {
slug: 'customers', slug: 'customers',
hooks: { hooks: {
afterChange: [async ({ doc }) => { afterChange: [
await payload.update({ async ({ doc }) => {
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop! await payload.update({
collection: 'customers', // DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
id: doc.id, collection: 'customers',
data: { id: doc.id,
...(await fetchCustomerData(data.customerID)) data: {
}, ...(await fetchCustomerData(data.customerID)),
}); },
}], })
},
],
}, },
fields: [ /* ... */ ], fields: [
}; /* ... */
],
}
``` ```
Instead of the above, we need to tell the `afterChange` hook to not run again if it performs the update (and thus not update itself again). We can solve that with context. Instead of the above, we need to tell the `afterChange` hook to not run again if it performs the update (and thus not update itself again). We can solve that with context.
@@ -84,26 +94,30 @@ Fixed example:
const MyCollection: CollectionConfig = { const MyCollection: CollectionConfig = {
slug: 'slug', slug: 'slug',
hooks: { hooks: {
afterChange: [async ({ context, doc }) => { afterChange: [
// return if flag was previously set async ({ context, doc }) => {
if (context.triggerAfterChange === false) { // return if flag was previously set
return; if (context.triggerAfterChange === false) {
} return
await payload.update({ }
collection: contextHooksSlug, await payload.update({
id: doc.id, collection: contextHooksSlug,
data: { id: doc.id,
...(await fetchCustomerData(data.customerID)) data: {
}, ...(await fetchCustomerData(data.customerID)),
context: { },
// set a flag to prevent from running again context: {
triggerAfterChange: false, // set a flag to prevent from running again
}, triggerAfterChange: false,
}); },
}], })
},
],
}, },
fields: [ /* ... */ ], fields: [
}; /* ... */
],
}
``` ```
## Typing context ## Typing context
@@ -113,12 +127,12 @@ The default typescript interface for `context` is `{ [key: string]: unknown }`.
This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file: This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file:
```ts ```ts
import { RequestContext as OriginalRequestContext } from 'payload'; import { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' { declare module 'payload' {
// Create a new interface that merges your additional fields with the original one // Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext { export interface RequestContext extends OriginalRequestContext {
myObject?: string; myObject?: string
// ... // ...
} }
} }

View File

@@ -26,6 +26,7 @@ Field-level hooks offer incredible potential for encapsulating your logic. They
## Config ## Config
Example field configuration: Example field configuration:
```ts ```ts
import { Field } from 'payload/types'; import { Field } from 'payload/types';
@@ -48,8 +49,12 @@ const ExampleField: Field = {
All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on which field hook you are utilizing. All field-level hooks are formatted to accept the same arguments, although some arguments may be `undefined` based on which field hook you are utilizing.
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong><br /> <strong>Tip:</strong>
It's a good idea to conditionally scope your logic based on which operation is executing. For example, if you are writing a <strong>beforeChange</strong> hook, you may want to perform different logic based on if the current <strong>operation</strong> is <strong>create</strong> or <strong>update</strong>. <br />
It's a good idea to conditionally scope your logic based on which operation is executing. For
example, if you are writing a <strong>beforeChange</strong> hook, you may want to perform
different logic based on if the current <strong>operation</strong> is <strong>create</strong> or{' '}
<strong>update</strong>.
</Banner> </Banner>
#### Arguments #### Arguments
@@ -57,7 +62,7 @@ All field-level hooks are formatted to accept the same arguments, although some
Field Hooks receive one `args` argument that contains the following properties: Field Hooks receive one `args` argument that contains the following properties:
| Option | Description | | Option | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. | | **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. |
| **`siblingData`** | The sibling data passed to a field that the hook is running against. | | **`siblingData`** | The sibling data passed to a field that the hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. | | **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
@@ -74,8 +79,11 @@ Field Hooks receive one `args` argument that contains the following properties:
All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may optionally return the value that should be used within the field. All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may optionally return the value that should be used within the field.
<Banner type="warning"> <Banner type="warning">
<strong>Important</strong><br/> <strong>Important</strong>
Due to GraphQL's typed nature, you should never change the type of data that you return from a field, otherwise GraphQL will produce errors. If you need to change the shape or type of data, reconsider Field Hooks and instead evaluate if Collection / Global hooks might suit you better. <br />
Due to GraphQL's typed nature, you should never change the type of data that you return from a
field, otherwise GraphQL will produce errors. If you need to change the shape or type of data,
reconsider Field Hooks and instead evaluate if Collection / Global hooks might suit you better.
</Banner> </Banner>
## TypeScript ## TypeScript
@@ -83,14 +91,14 @@ All field hooks can optionally modify the return value of the field before the o
Payload exports a type for field hooks which can be accessed and used as follows: Payload exports a type for field hooks which can be accessed and used as follows:
```ts ```ts
import type { FieldHook } from 'payload/types'; import type { FieldHook } from 'payload/types'
// Field hook type is a generic that takes three arguments: // Field hook type is a generic that takes three arguments:
// 1: The document type // 1: The document type
// 2: The value type // 2: The value type
// 3: The sibling data type // 3: The sibling data type
type ExampleFieldHook = FieldHook<ExampleDocumentType, string, SiblingDataType>; type ExampleFieldHook = FieldHook<ExampleDocumentType, string, SiblingDataType>
const exampleFieldHook: ExampleFieldHook = (args) => { const exampleFieldHook: ExampleFieldHook = (args) => {
const { const {
@@ -100,10 +108,10 @@ const exampleFieldHook: ExampleFieldHook = (args) => {
originalDoc, // Typed as ExampleDocumentType originalDoc, // Typed as ExampleDocumentType
operation, operation,
req, req,
} = args; } = args
// Do something here... // Do something here...
return value; // should return a string as typed above, undefined, or null return value // should return a string as typed above, undefined, or null
} }
``` ```

View File

@@ -19,6 +19,7 @@ Globals feature the ability to define the following hooks:
All Global Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs. All Global Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
`globals/example-hooks.js` `globals/example-hooks.js`
```ts ```ts
import { GlobalConfig } from 'payload/types'; import { GlobalConfig } from 'payload/types';
@@ -49,7 +50,7 @@ const beforeValidateHook: GlobalBeforeValidateHook = async ({
req, // full express request req, // full express request
originalDoc, // original document originalDoc, // original document
}) => { }) => {
return data; // Return data to update the document with return data // Return data to update the document with
} }
``` ```
@@ -65,7 +66,7 @@ const beforeChangeHook: GlobalBeforeChangeHook = async ({
req, // full express request req, // full express request
originalDoc, // original document originalDoc, // original document
}) => { }) => {
return data; // Return data to update the document with return data // Return data to update the document with
} }
``` ```
@@ -81,7 +82,7 @@ const afterChangeHook: GlobalAfterChangeHook = async ({
previousDoc, // document data before updating the collection previousDoc, // document data before updating the collection
req, // full express request req, // full express request
}) => { }) => {
return data; return data
} }
``` ```
@@ -123,5 +124,5 @@ import type {
GlobalAfterChangeHook, GlobalAfterChangeHook,
GlobalBeforeReadHook, GlobalBeforeReadHook,
GlobalAfterReadHook, GlobalAfterReadHook,
} from 'payload/types'; } from 'payload/types'
``` ```

View File

@@ -7,7 +7,9 @@ keywords: hooks, overview, config, configuration, documentation, Content Managem
--- ---
<Banner type="info"> <Banner type="info">
Hooks are powerful ways to tie into existing Payload actions in order to add your own logic like integrating with third-party APIs, adding auto-generated data, or modifing Payload's base functionality. Hooks are powerful ways to tie into existing Payload actions in order to add your own logic like
integrating with third-party APIs, adding auto-generated data, or modifing Payload's base
functionality.
</Banner> </Banner>
**With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework.** **With Hooks, you can transform Payload from a traditional CMS into a fully-fledged application framework.**

View File

@@ -11,10 +11,9 @@ keywords: vercel, vercel visual editing, visual editing, content source maps, Co
![Versions](/images/docs/vercel-visual-editing.jpg) ![Versions](/images/docs/vercel-visual-editing.jpg)
<Banner type="warning"> <Banner type="warning">
Vercel Visual Editing is an enterprise-only feature and only available for Vercel Visual Editing is an enterprise-only feature and only available for deployments hosted on
deployments hosted on Vercel. If you are an existing enterprise customer, Vercel. If you are an existing enterprise customer, [contact our sales
[contact our sales team](https://payloadcms.com/for-enterprise) for help with team](https://payloadcms.com/for-enterprise) for help with your integration.
your integration.
</Banner> </Banner>
### How it works ### How it works
@@ -66,10 +65,10 @@ export default config
Now in your Next.js app, include the `?encodeSourceMaps=true` parameter in any of your API requests. For performance reasons, this should only be done when in draft mode or on preview deployments. Now in your Next.js app, include the `?encodeSourceMaps=true` parameter in any of your API requests. For performance reasons, this should only be done when in draft mode or on preview deployments.
```ts ```ts
if (isDraftMode || process.env.VERCEL_ENV === "preview") { if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
const res = await fetch( const res = await fetch(
`${process.env.NEXT_PUBLIC_PAYLOAD_CMS_URL}/api/pages?where[slug][equals]=${slug}&encodeSourceMaps=true` `${process.env.NEXT_PUBLIC_PAYLOAD_CMS_URL}/api/pages?where[slug][equals]=${slug}&encodeSourceMaps=true`,
); )
} }
``` ```
@@ -88,8 +87,8 @@ To see Visual Editing on your site, you first need to visit any preview deployme
The plugin does not encode `date` fields by default, but for some cases like text that uses negative CSS letter-spacing, it may be necessary to split the encoded data out from the rendered text. This way you can safely use the cleaned data as expected. The plugin does not encode `date` fields by default, but for some cases like text that uses negative CSS letter-spacing, it may be necessary to split the encoded data out from the rendered text. This way you can safely use the cleaned data as expected.
```ts ```ts
import { vercelStegaSplit } from "@vercel/stega"; import { vercelStegaSplit } from '@vercel/stega'
const { cleaned, encoded } = vercelStegaSplit(text); const { cleaned, encoded } = vercelStegaSplit(text)
``` ```
##### Blocks ##### Blocks

View File

@@ -11,11 +11,10 @@ The Payload Local API gives you the ability to execute the same operations that
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br /> <br />
The Local API is incredibly powerful when used with server-side rendering app The Local API is incredibly powerful when used with server-side rendering app frameworks like
frameworks like NextJS. With other headless CMS, you need to request your data NextJS. With other headless CMS, you need to request your data from third-party servers which can
from third-party servers which can add significant loading time to your add significant loading time to your server-rendered pages. With Payload, you don't have to leave
server-rendered pages. With Payload, you don't have to leave your server to your server to gather the data you need. It can be incredibly fast and is definitely a game
gather the data you need. It can be incredibly fast and is definitely a game
changer. changer.
</Banner> </Banner>
@@ -36,14 +35,14 @@ You can import or require `payload` into your own files after it's been initiali
Example: Example:
```ts ```ts
import payload from "payload"; import payload from 'payload'
import { CollectionAfterChangeHook } from "payload/types"; import { CollectionAfterChangeHook } from 'payload/types'
const afterChangeHook: CollectionAfterChangeHook = async () => { const afterChangeHook: CollectionAfterChangeHook = async () => {
const posts = await payload.find({ const posts = await payload.find({
collection: "posts", collection: 'posts',
}); })
}; }
``` ```
##### Accessing from the `req` ##### Accessing from the `req`
@@ -53,40 +52,37 @@ Payload is available anywhere you have access to the Express `req` - including w
Example: Example:
```ts ```ts
const afterChangeHook: CollectionAfterChangeHook = async ({ const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } }) => {
req: { payload },
}) => {
const posts = await payload.find({ const posts = await payload.find({
collection: "posts", collection: 'posts',
}); })
}; }
``` ```
### Local options available ### Local options available
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in. You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
| Local Option | Description | | Local Option | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. | | `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
| `data` | The data to use within the operation. Required for `create`, `update`. | | `data` | The data to use within the operation. Required for `create`, `update`. |
| `depth` | [Control auto-population](/docs/getting-started/concepts#depth) of nested relationship and upload fields. | | `depth` | [Control auto-population](/docs/getting-started/concepts#depth) of nested relationship and upload fields. |
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. | | `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. | | `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. | | `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. | | `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. | | `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
| `pagination` | Set to false to return all documents and avoid querying for document counts. | | `pagination` | Set to false to return all documents and avoid querying for document counts. |
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. | | `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
_There are more options available on an operation by operation basis outlined below._ _There are more options available on an operation by operation basis outlined below._
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
By default, all access control checks are disabled in the Local API, but you By default, all access control checks are disabled in the Local API, but you can re-enable them if
can re-enable them if you'd like, as well as pass a specific user to run the you'd like, as well as pass a specific user to run the operation with.
operation with.
</Banner> </Banner>
## Collections ## Collections
@@ -98,13 +94,13 @@ The following Collection operations are available through the Local API:
```js ```js
// The created Post document is returned // The created Post document is returned
const post = await payload.create({ const post = await payload.create({
collection: "posts", // required collection: 'posts', // required
data: { data: {
// required // required
title: "sure", title: 'sure',
description: "maybe", description: 'maybe',
}, },
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUserDoc, user: dummyUserDoc,
overrideAccess: true, overrideAccess: true,
@@ -117,12 +113,12 @@ const post = await payload.create({
// If your collection supports uploads, you can upload // If your collection supports uploads, you can upload
// a file directly through the Local API by providing // a file directly through the Local API by providing
// its full, absolute file path. // its full, absolute file path.
filePath: path.resolve(__dirname, "./path-to-image.jpg"), filePath: path.resolve(__dirname, './path-to-image.jpg'),
// Alternatively, you can directly pass a File, // Alternatively, you can directly pass a File,
// if file is provided, filePath will be omitted // if file is provided, filePath will be omitted
file: uploadedFile, file: uploadedFile,
}); })
``` ```
#### Find #### Find
@@ -131,18 +127,18 @@ const post = await payload.create({
// Result will be a paginated set of Posts. // Result will be a paginated set of Posts.
// See /docs/queries/pagination for more. // See /docs/queries/pagination for more.
const result = await payload.find({ const result = await payload.find({
collection: "posts", // required collection: 'posts', // required
depth: 2, depth: 2,
page: 1, page: 1,
limit: 10, limit: 10,
where: {}, // pass a `where` query here where: {}, // pass a `where` query here
sort: "-title", sort: '-title',
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Find by ID #### Find by ID
@@ -150,15 +146,15 @@ const result = await payload.find({
```js ```js
// Result will be a Post document. // Result will be a Post document.
const result = await payload.findByID({ const result = await payload.findByID({
collection: "posts", // required collection: 'posts', // required
id: "507f1f77bcf86cd799439011", // required id: '507f1f77bcf86cd799439011', // required
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Update by ID #### Update by ID
@@ -166,15 +162,15 @@ const result = await payload.findByID({
```js ```js
// Result will be the updated Post document. // Result will be the updated Post document.
const result = await payload.update({ const result = await payload.update({
collection: "posts", // required collection: 'posts', // required
id: "507f1f77bcf86cd799439011", // required id: '507f1f77bcf86cd799439011', // required
data: { data: {
// required // required
title: "sure", title: 'sure',
description: "maybe", description: 'maybe',
}, },
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
@@ -183,13 +179,13 @@ const result = await payload.update({
// If your collection supports uploads, you can upload // If your collection supports uploads, you can upload
// a file directly through the Local API by providing // a file directly through the Local API by providing
// its full, absolute file path. // its full, absolute file path.
filePath: path.resolve(__dirname, "./path-to-image.jpg"), filePath: path.resolve(__dirname, './path-to-image.jpg'),
// If you are uploading a file and would like to replace // If you are uploading a file and would like to replace
// the existing file instead of generating a new filename, // the existing file instead of generating a new filename,
// you can set the following property to `true` // you can set the following property to `true`
overwriteExistingFiles: true, overwriteExistingFiles: true,
}); })
``` ```
#### Update Many #### Update Many
@@ -201,18 +197,18 @@ const result = await payload.update({
// errors: [], // each error also includes the id of the document // errors: [], // each error also includes the id of the document
// } // }
const result = await payload.update({ const result = await payload.update({
collection: "posts", // required collection: 'posts', // required
where: { where: {
// required // required
fieldName: { equals: "value" }, fieldName: { equals: 'value' },
}, },
data: { data: {
// required // required
title: "sure", title: 'sure',
description: "maybe", description: 'maybe',
}, },
depth: 0, depth: 0,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
@@ -221,13 +217,13 @@ const result = await payload.update({
// If your collection supports uploads, you can upload // If your collection supports uploads, you can upload
// a file directly through the Local API by providing // a file directly through the Local API by providing
// its full, absolute file path. // its full, absolute file path.
filePath: path.resolve(__dirname, "./path-to-image.jpg"), filePath: path.resolve(__dirname, './path-to-image.jpg'),
// If you are uploading a file and would like to replace // If you are uploading a file and would like to replace
// the existing file instead of generating a new filename, // the existing file instead of generating a new filename,
// you can set the following property to `true` // you can set the following property to `true`
overwriteExistingFiles: true, overwriteExistingFiles: true,
}); })
``` ```
#### Delete #### Delete
@@ -235,15 +231,15 @@ const result = await payload.update({
```js ```js
// Result will be the now-deleted Post document. // Result will be the now-deleted Post document.
const result = await payload.delete({ const result = await payload.delete({
collection: "posts", // required collection: 'posts', // required
id: "507f1f77bcf86cd799439011", // required id: '507f1f77bcf86cd799439011', // required
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Delete Many #### Delete Many
@@ -255,18 +251,18 @@ const result = await payload.delete({
// errors: [], // any errors that occurred, including the id of the errored on document // errors: [], // any errors that occurred, including the id of the errored on document
// } // }
const result = await payload.delete({ const result = await payload.delete({
collection: "posts", // required collection: 'posts', // required
where: { where: {
// required // required
fieldName: { equals: "value" }, fieldName: { equals: 'value' },
}, },
depth: 0, depth: 0,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
## Auth Operations ## Auth Operations
@@ -284,20 +280,20 @@ If a collection has [`Authentication`](/docs/authentication/overview) enabled, a
// } // }
const result = await payload.login({ const result = await payload.login({
collection: "users", // required collection: 'users', // required
data: { data: {
// required // required
email: "dev@payloadcms.com", email: 'dev@payloadcms.com',
password: "rip", password: 'rip',
}, },
req: req, // pass an Express `req` which will be provided to all hooks req: req, // pass an Express `req` which will be provided to all hooks
res: res, // used to automatically set an HTTP-only auth cookie res: res, // used to automatically set an HTTP-only auth cookie
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Forgot Password #### Forgot Password
@@ -305,13 +301,13 @@ const result = await payload.login({
```js ```js
// Returned token will allow for a password reset // Returned token will allow for a password reset
const token = await payload.forgotPassword({ const token = await payload.forgotPassword({
collection: "users", // required collection: 'users', // required
data: { data: {
// required // required
email: "dev@payloadcms.com", email: 'dev@payloadcms.com',
}, },
req: req, // pass an Express `req` which will be provided to all hooks req: req, // pass an Express `req` which will be provided to all hooks
}); })
``` ```
#### Reset Password #### Reset Password
@@ -323,14 +319,14 @@ const token = await payload.forgotPassword({
// user: { ... } // the user document that just logged in // user: { ... } // the user document that just logged in
// } // }
const result = await payload.forgotPassword({ const result = await payload.forgotPassword({
collection: "users", // required collection: 'users', // required
data: { data: {
// required // required
token: "afh3o2jf2p3f...", // the token generated from the forgotPassword operation token: 'afh3o2jf2p3f...', // the token generated from the forgotPassword operation
}, },
req: req, // pass an Express `req` which will be provided to all hooks req: req, // pass an Express `req` which will be provided to all hooks
res: res, // used to automatically set an HTTP-only auth cookie res: res, // used to automatically set an HTTP-only auth cookie
}); })
``` ```
#### Unlock #### Unlock
@@ -338,14 +334,14 @@ const result = await payload.forgotPassword({
```js ```js
// Returned result will be a boolean representing success or failure // Returned result will be a boolean representing success or failure
const result = await payload.unlock({ const result = await payload.unlock({
collection: "users", // required collection: 'users', // required
data: { data: {
// required // required
email: "dev@payloadcms.com", email: 'dev@payloadcms.com',
}, },
req: req, // pass an Express `req` which will be provided to all hooks req: req, // pass an Express `req` which will be provided to all hooks
overrideAccess: true, overrideAccess: true,
}); })
``` ```
#### Verify #### Verify
@@ -353,9 +349,9 @@ const result = await payload.unlock({
```js ```js
// Returned result will be a boolean representing success or failure // Returned result will be a boolean representing success or failure
const result = await payload.verify({ const result = await payload.verify({
collection: "users", // required collection: 'users', // required
token: "afh3o2jf2p3f...", // the token saved on the user as `_verificationToken` token: 'afh3o2jf2p3f...', // the token saved on the user as `_verificationToken`
}); })
``` ```
## Globals ## Globals
@@ -367,14 +363,14 @@ The following Global operations are available through the Local API:
```js ```js
// Result will be the Header Global. // Result will be the Header Global.
const result = await payload.findGlobal({ const result = await payload.findGlobal({
slug: "header", // required slug: 'header', // required
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Update #### Update
@@ -382,25 +378,25 @@ const result = await payload.findGlobal({
```js ```js
// Result will be the updated Header Global. // Result will be the updated Header Global.
const result = await payload.updateGlobal({ const result = await payload.updateGlobal({
slug: "header", // required slug: 'header', // required
data: { data: {
// required // required
nav: [ nav: [
{ {
url: "https://google.com", url: 'https://google.com',
}, },
{ {
url: "https://payloadcms.com", url: 'https://payloadcms.com',
}, },
], ],
}, },
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
## Example Script using Local API ## Example Script using Local API
@@ -408,36 +404,36 @@ const result = await payload.updateGlobal({
The Local API is especially useful for running scripts The Local API is especially useful for running scripts
```ts ```ts
import payload from "payload"; import payload from 'payload'
import path from "path"; import path from 'path'
import dotenv from "dotenv"; import dotenv from 'dotenv'
dotenv.config({ dotenv.config({
path: path.resolve(__dirname, "../.env"), path: path.resolve(__dirname, '../.env'),
}); })
const { PAYLOAD_SECRET, MONGODB_URI } = process.env; const { PAYLOAD_SECRET, MONGODB_URI } = process.env
const doAction = async (): Promise<void> => { const doAction = async (): Promise<void> => {
await payload.init({ await payload.init({
secret: PAYLOAD_SECRET, secret: PAYLOAD_SECRET,
mongoURL: MONGODB_URI, mongoURL: MONGODB_URI,
local: true, // Enables local mode, doesn't spin up a server or frontend local: true, // Enables local mode, doesn't spin up a server or frontend
}); })
// Perform any Local API operations here // Perform any Local API operations here
await payload.find({ await payload.find({
collection: "posts", collection: 'posts',
// where: {} // optional // where: {} // optional
}); })
await payload.create({ await payload.create({
collection: "posts", collection: 'posts',
data: {}, data: {},
}); })
}; }
doAction(); doAction()
``` ```
## TypeScript ## TypeScript
@@ -449,12 +445,12 @@ Here is an example of usage:
```ts ```ts
// Properly inferred as `Post` type // Properly inferred as `Post` type
const post = await payload.create({ const post = await payload.create({
collection: "posts", collection: 'posts',
// Data will now be typed as Post and give you type hints // Data will now be typed as Post and give you type hints
data: { data: {
title: "my title", title: 'my title',
description: "my description", description: 'my description',
}, },
}); })
``` ```

View File

@@ -9,7 +9,9 @@ keywords: plugins, config, configuration, extensions, custom, documentation, Con
Payload comes with a built-in Plugins infrastructure that allows developers to build their own modular and easily reusable sets of functionality. Payload comes with a built-in Plugins infrastructure that allows developers to build their own modular and easily reusable sets of functionality.
<Banner type="success"> <Banner type="success">
Because we rely on a simple config-based structure, Payload plugins simply take in a user's existing config and return a modified config with new fields, hooks, collections, admin views, or anything else you can think of. Because we rely on a simple config-based structure, Payload plugins simply take in a user's
existing config and return a modified config with new fields, hooks, collections, admin views, or
anything else you can think of.
</Banner> </Banner>
Writing plugins is no more complex than writing regular JavaScript. If you know how [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works and are up to speed with Payload concepts, writing a plugin will be a breeze. Writing plugins is no more complex than writing regular JavaScript. If you know how [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works and are up to speed with Payload concepts, writing a plugin will be a breeze.
@@ -30,30 +32,30 @@ Writing plugins is no more complex than writing regular JavaScript. If you know
The base Payload config allows for a `plugins` property which takes an `array` of [`Plugins`](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21). The base Payload config allows for a `plugins` property which takes an `array` of [`Plugins`](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21).
```js ```js
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
// note: these plugins are not real (yet?) // note: these plugins are not real (yet?)
import addLastModified from 'payload-add-last-modified'; import addLastModified from 'payload-add-last-modified'
import passwordProtect from 'payload-password-protect'; import passwordProtect from 'payload-password-protect'
const config = buildConfig({ const config = buildConfig({
collections: [ collections: [
{ {
slug: 'pages', slug: 'pages',
fields: [ fields: [
{ {
name: 'title', name: 'title',
type: 'text', type: 'text',
required: true, required: true,
}, },
{ {
name: 'content', name: 'content',
type: 'richText', type: 'richText',
required: true, required: true,
} },
] ],
} },
], ],
plugins: [ plugins: [
// Many plugins require options to be passed. // Many plugins require options to be passed.
// In the following example, we call the function // In the following example, we call the function
@@ -67,10 +69,10 @@ const config = buildConfig({
// .. // ..
// To understand how to use the plugins you're interested in, // To understand how to use the plugins you're interested in,
// consult their corresponding documentation // consult their corresponding documentation
] ],
}); })
export default config; export default config
``` ```
#### When Plugins are initialized #### When Plugins are initialized
@@ -84,21 +86,20 @@ After all plugins are executed, the full config with all plugins will be sanitiz
Here is an example for how to automatically add a `lastModifiedBy` field to all Payload collections using a Plugin written in TypeScript. Here is an example for how to automatically add a `lastModifiedBy` field to all Payload collections using a Plugin written in TypeScript.
```ts ```ts
import { Config, Plugin } from 'payload/config'; import { Config, Plugin } from 'payload/config'
const addLastModified: Plugin = (incomingConfig: Config): Config => { const addLastModified: Plugin = (incomingConfig: Config): Config => {
// Find all incoming auth-enabled collections // Find all incoming auth-enabled collections
// so we can create a lastModifiedBy relationship field // so we can create a lastModifiedBy relationship field
// to all auth collections // to all auth collections
const authEnabledCollections = incomingConfig.collections.filter( const authEnabledCollections = incomingConfig.collections.filter((collection) =>
collection => Boolean(collection.auth) Boolean(collection.auth),
); )
// Spread the existing config // Spread the existing config
const config: Config = { const config: Config = {
...incomingConfig, ...incomingConfig,
collections: incomingConfig.collections.map((collection) => { collections: incomingConfig.collections.map((collection) => {
// Spread each item that we are modifying, // Spread each item that we are modifying,
// and add our new field - complete with // and add our new field - complete with
// hooks and proper admin UI config // hooks and proper admin UI config
@@ -116,7 +117,7 @@ const addLastModified: Plugin = (incomingConfig: Config): Config => {
value: req?.user?.id, value: req?.user?.id,
relationTo: req?.user?.collection, relationTo: req?.user?.collection,
}), }),
] ],
}, },
admin: { admin: {
position: 'sidebar', position: 'sidebar',
@@ -124,14 +125,14 @@ const addLastModified: Plugin = (incomingConfig: Config): Config => {
}, },
}, },
], ],
}; }
}), }),
}; }
return config; return config
}; }
export default addLastModified; export default addLastModified
``` ```
### Available Plugins ### Available Plugins

View File

@@ -7,7 +7,8 @@ keywords: deployment, production, config, configuration, documentation, Content
--- ---
<Banner type="success"> <Banner type="success">
So you've developed a Payload app, it's fully tested, and running great locally. Now it's time to launch. <strong>Awesome! Great work!</strong> Now, what's next? So you've developed a Payload app, it's fully tested, and running great locally. Now it's time to
launch. <strong>Awesome! Great work!</strong> Now, what's next?
</Banner> </Banner>
There are many ways to deploy Payload to a production environment. When evaluating how you will deploy Payload, you need to consider these main aspects: There are many ways to deploy Payload to a production environment. When evaluating how you will deploy Payload, you need to consider these main aspects:
@@ -35,7 +36,13 @@ When you initialize Payload, you provide it with a `secret` property. This prope
Because _**you**_ are in complete control of who can do what with your data, you should double and triple-check that you wield that power responsibly before deploying to Production. Because _**you**_ are in complete control of who can do what with your data, you should double and triple-check that you wield that power responsibly before deploying to Production.
<Banner type="error"> <Banner type="error">
<strong>By default, all Access Control functions require that a user is successfully logged in to Payload to create, read, update, or delete data.</strong> But, if you allow public user registration, for example, you will want to make sure that your access control functions are more strict - permitting <strong>only appropriate users</strong> to perform appropriate actions. <strong>
By default, all Access Control functions require that a user is successfully logged in to
Payload to create, read, update, or delete data.
</strong>{' '}
But, if you allow public user registration, for example, you will want to make sure that your
access control functions are more strict - permitting <strong>only appropriate users</strong> to
perform appropriate actions.
</Banner> </Banner>
##### Building the Admin panel ##### Building the Admin panel
@@ -83,8 +90,13 @@ If you are using a [persistent filesystem-based cloud host](#persistent-vs-ephem
Alternatively, you can rely on a third-party MongoDB host such as [MongoDB Atlas](https://www.mongodb.com/). With Atlas or a similar cloud provider, you can trust them to take care of your database's availability, security, redundancy, and backups. Alternatively, you can rely on a third-party MongoDB host such as [MongoDB Atlas](https://www.mongodb.com/). With Atlas or a similar cloud provider, you can trust them to take care of your database's availability, security, redundancy, and backups.
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong><br /> <strong>Note:</strong>
If versions are enabled and a collection has many documents you may need a minimum of an m10 mongoDB atlas cluster if you reach a sorting `exceeded memory limit` error to view a collection list in the admin UI. The limitations of the m2 and m5 tier clusters are here: [Atlas M0 (Free Cluster), M2, and M5 Limitations](https://www.mongodb.com/docs/atlas/reference/free-shared-limitations/?_ga=2.176267877.1329169847.1677683154-860992573.1647438381#operational-limitations). <br />
If versions are enabled and a collection has many documents you may need a minimum of an m10
mongoDB atlas cluster if you reach a sorting `exceeded memory limit` error to view a collection
list in the admin UI. The limitations of the m2 and m5 tier clusters are here: [Atlas M0 (Free
Cluster), M2, and M5
Limitations](https://www.mongodb.com/docs/atlas/reference/free-shared-limitations/?_ga=2.176267877.1329169847.1677683154-860992573.1647438381#operational-limitations).
</Banner> </Banner>
##### DocumentDB ##### DocumentDB
@@ -118,8 +130,10 @@ Alternatively, persistent filesystems will never delete your files and can be tr
- Many other more traditional web hosts - Many other more traditional web hosts
<Banner type="error"> <Banner type="error">
<strong>Warning:</strong><br /> <strong>Warning:</strong>
If you rely on Payload's <strong>Upload</strong> functionality, make sure you either use a host with a persistent filesystem or have an integration with a third-party file host like Amazon S3. <br />
If you rely on Payload's <strong>Upload</strong> functionality, make sure you either use a host
with a persistent filesystem or have an integration with a third-party file host like Amazon S3.
</Banner> </Banner>
##### Using ephemeral filesystem providers like Heroku ##### Using ephemeral filesystem providers like Heroku
@@ -184,13 +198,13 @@ CMD ["node", "dist/server.js"]
Here is an example of a docker-compose.yml file that can be used for development Here is an example of a docker-compose.yml file that can be used for development
```yml ```yml
version: "3" version: '3'
services: services:
payload: payload:
image: node:18-alpine image: node:18-alpine
ports: ports:
- "3000:3000" - '3000:3000'
volumes: volumes:
- .:/home/node/app - .:/home/node/app
- node_modules:/home/node/app/node_modules - node_modules:/home/node/app/node_modules
@@ -207,7 +221,7 @@ services:
mongo: mongo:
image: mongo:latest image: mongo:latest
ports: ports:
- "27017:27017" - '27017:27017'
command: command:
- --storageEngine=wiredTiger - --storageEngine=wiredTiger
volumes: volumes:

View File

@@ -18,16 +18,21 @@ Set the max number of failed login attempts before a user account is locked out
To prevent DDoS, brute-force, and similar attacks, you can set IP-based rate limits so that once a certain threshold of requests has been hit by a single IP, further requests from the same IP will be ignored. The Payload config `rateLimit` property accepts an object with the following properties: To prevent DDoS, brute-force, and similar attacks, you can set IP-based rate limits so that once a certain threshold of requests has been hit by a single IP, further requests from the same IP will be ignored. The Payload config `rateLimit` property accepts an object with the following properties:
| Option | Description | | Option | Description |
| ---------------------------- | ----------- | | ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **`window`** | Time in milliseconds to track requests per IP. Defaults to `90000` (15 minutes). | | **`window`** | Time in milliseconds to track requests per IP. Defaults to `90000` (15 minutes). |
| **`max`** | Number of requests served from a single IP before limiting. Defaults to `500`. | | **`max`** | Number of requests served from a single IP before limiting. Defaults to `500`. |
| **`skip`** | Express middleware function that can return true (or promise resulting in true) that will bypass limit. | | **`skip`** | Express middleware function that can return true (or promise resulting in true) that will bypass limit. |
| **`trustProxy`** | True or false, to enable to allow requests to pass through a proxy such as a load balancer or an `nginx` reverse proxy. | | **`trustProxy`** | True or false, to enable to allow requests to pass through a proxy such as a load balancer or an `nginx` reverse proxy. |
<Banner type="warning"> <Banner type="warning">
<strong>Warning:</strong><br/> <strong>Warning:</strong>
Very commonly, NodeJS apps are served behind `nginx` reverse proxies and similar. If you use rate-limiting while you're behind a proxy, <strong>all</strong> IP addresses from everyone that uses your API will appear as if they are from a local origin (127.0.0.1), and your users will get rate-limited very quickly without cause. If you plan to host your app behind a proxy, make sure you set <strong>trustProxy</strong> to <strong>true</strong>. <br />
Very commonly, NodeJS apps are served behind `nginx` reverse proxies and similar. If you use
rate-limiting while you're behind a proxy, <strong>all</strong> IP addresses from everyone that
uses your API will appear as if they are from a local origin (127.0.0.1), and your users will get
rate-limited very quickly without cause. If you plan to host your app behind a proxy, make sure
you set <strong>trustProxy</strong> to <strong>true</strong>.
</Banner> </Banner>
### Max Depth ### Max Depth

View File

@@ -9,7 +9,11 @@ keywords: query, documents, overview, documentation, Content Management System,
Payload provides an extremely granular querying language through all APIs. Each API takes the same syntax and fully supports all options. Payload provides an extremely granular querying language through all APIs. Each API takes the same syntax and fully supports all options.
<Banner> <Banner>
<strong>Here, "querying" relates to filtering or searching through documents within a Collection.</strong> You can build queries to pass to Find operations as well as to [restrict which documents certain users can access](/docs/access-control/overview) via access control functions. <strong>
Here, "querying" relates to filtering or searching through documents within a Collection.
</strong>{' '}
You can build queries to pass to Find operations as well as to [restrict which documents certain
users can access](/docs/access-control/overview) via access control functions.
</Banner> </Banner>
### Simple queries ### Simple queries
@@ -17,22 +21,22 @@ Payload provides an extremely granular querying language through all APIs. Each
For example, say you have a collection as follows: For example, say you have a collection as follows:
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
export const Post: CollectionConfig = { export const Post: CollectionConfig = {
slug: "posts", slug: 'posts',
fields: [ fields: [
{ {
name: "color", name: 'color',
type: "select", type: 'select',
options: ["mint", "dark-gray", "white"], options: ['mint', 'dark-gray', 'white'],
}, },
{ {
name: "featured", name: 'featured',
type: "checkbox", type: 'checkbox',
}, },
], ],
}; }
``` ```
You may eventually have a lot of documents within this Collection. If you wanted to find only documents with `color` equal to `mint`, you could write a query as follows: You may eventually have a lot of documents within this Collection. If you wanted to find only documents with `color` equal to `mint`, you could write a query as follows:
@@ -41,9 +45,9 @@ You may eventually have a lot of documents within this Collection. If you wanted
const query = { const query = {
color: { color: {
// property name to filter on // property name to filter on
equals: "mint", // operator to use and value to compare against equals: 'mint', // operator to use and value to compare against
}, },
}; }
``` ```
The above example demonstrates a simple query but you can get much more complex. The above example demonstrates a simple query but you can get much more complex.
@@ -68,7 +72,9 @@ The above example demonstrates a simple query but you can get much more complex.
<Banner type="success"> <Banner type="success">
<strong>Tip</strong>:<br /> <strong>Tip</strong>:<br />
If you know your users will be querying on certain fields a lot, you can add <strong>index: true</strong> to a field's config which will speed up searches using that field immensely. If you know your users will be querying on certain fields a lot, you can add <strong>
index: true
</strong> to a field's config which will speed up searches using that field immensely.
</Banner> </Banner>
### And / Or Logic ### And / Or Logic
@@ -81,7 +87,7 @@ const query = {
// array of OR conditions // array of OR conditions
{ {
color: { color: {
equals: "mint", equals: 'mint',
}, },
}, },
{ {
@@ -89,7 +95,7 @@ const query = {
// nested array of AND conditions // nested array of AND conditions
{ {
color: { color: {
equals: "white", equals: 'white',
}, },
}, },
{ {
@@ -100,7 +106,7 @@ const query = {
], ],
}, },
], ],
}; }
``` ```
Written in plain English, if the above query were passed to a `find` operation, it would translate to finding posts where either the `color` is `mint` OR the `color` is `white` AND `featured` is set to false. Written in plain English, if the above query were passed to a `find` operation, it would translate to finding posts where either the `color` is `mint` OR the `color` is `white` AND `featured` is set to false.
@@ -111,11 +117,11 @@ When working with nested properties, which can happen when using relational fiel
```js ```js
const query = { const query = {
"artists.featured": { 'artists.featured': {
// nested property name to filter on // nested property name to filter on
exists: true, // operator to use and boolean value that needs to be true exists: true, // operator to use and boolean value that needs to be true
}, },
}; }
``` ```
### GraphQL Find Queries ### GraphQL Find Queries
@@ -148,29 +154,27 @@ This one isn't too bad, but more complex queries get unavoidably more difficult
**For example, using fetch:** **For example, using fetch:**
```js ```js
import qs from "qs"; import qs from 'qs'
const query = { const query = {
color: { color: {
equals: "mint", equals: 'mint',
}, },
// This query could be much more complex // This query could be much more complex
// and QS would handle it beautifully // and QS would handle it beautifully
}; }
const getPosts = async () => { const getPosts = async () => {
const stringifiedQuery = qs.stringify( const stringifiedQuery = qs.stringify(
{ {
where: query, // ensure that `qs` adds the `where` property, too! where: query, // ensure that `qs` adds the `where` property, too!
}, },
{ addQueryPrefix: true } { addQueryPrefix: true },
); )
const response = await fetch( const response = await fetch(`http://localhost:3000/api/posts${stringifiedQuery}`)
`http://localhost:3000/api/posts${stringifiedQuery}`
);
// Continue to handle the response below... // Continue to handle the response below...
}; }
``` ```
### Local API Queries ### Local API Queries
@@ -180,16 +184,16 @@ The Local API's `find` operation accepts an object exactly how you write it. For
```js ```js
const getPosts = async () => { const getPosts = async () => {
const posts = await payload.find({ const posts = await payload.find({
collection: "posts", collection: 'posts',
where: { where: {
color: { color: {
equals: "mint", equals: 'mint',
}, },
}, },
}); })
return posts; return posts
}; }
``` ```
## Sort ## Sort
@@ -216,10 +220,10 @@ query {
```js ```js
const getPosts = async () => { const getPosts = async () => {
const posts = await payload.find({ const posts = await payload.find({
collection: "posts", collection: 'posts',
sort: "-createdAt", sort: '-createdAt',
}); })
return posts; return posts
}; }
``` ```

View File

@@ -10,20 +10,21 @@ All collection `find` queries are paginated automatically. Responses are returne
**`Find` response properties:** **`Find` response properties:**
| Property | Description | | Property | Description |
| ------------- | ---------------------------------------------------------- | | ------------- | --------------------------------------------------------- |
| docs | Array of documents in the collection | | docs | Array of documents in the collection |
| totalDocs | Total available documents within the collection | | totalDocs | Total available documents within the collection |
| limit | Limit query parameter - defaults to `10` | | limit | Limit query parameter - defaults to `10` |
| totalPages | Total pages available, based upon the `limit` queried for | | totalPages | Total pages available, based upon the `limit` queried for |
| page | Current page number | | page | Current page number |
| pagingCounter | `number` of the first doc on the current page | | pagingCounter | `number` of the first doc on the current page |
| hasPrevPage | `true/false` if previous page exists | | hasPrevPage | `true/false` if previous page exists |
| hasNextPage | `true/false` if next page exists | | hasNextPage | `true/false` if next page exists |
| prevPage | `number` of previous page, `null` if it doesn't exist | | prevPage | `number` of previous page, `null` if it doesn't exist |
| nextPage | `number` of next page, `null` if it doesn't exist | | nextPage | `number` of next page, `null` if it doesn't exist |
**Example response:** **Example response:**
```json ```json
{ {
// Document Array // highlight-line // Document Array // highlight-line
@@ -54,7 +55,7 @@ All collection `find` queries are paginated automatically. Responses are returne
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application: All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
| Control | Description | | Control | Description |
| --------- | ----------------------------------------------------------------------------------------- | | ------- | --------------------------------------- |
| `limit` | Limits the number of documents returned | | `limit` | Limits the number of documents returned |
| `page` | Get a specific page number | | `page` | Get a specific page number |

View File

@@ -7,8 +7,7 @@ keywords: rest, api, documentation, Content Management System, cms, headless, ja
--- ---
<Banner> <Banner>
A fully functional REST API is automatically generated from your Collection A fully functional REST API is automatically generated from your Collection and Global configs.
and Global configs.
</Banner> </Banner>
All Payload API routes are mounted prefixed to your config's `routes.api` URL segment (default: `/api`). All Payload API routes are mounted prefixed to your config's `routes.api` URL segment (default: `/api`).
@@ -439,45 +438,45 @@ Globals cannot be created or deleted, so there are only two REST endpoints opene
<RestExamples <RestExamples
data={[ data={[
{ {
operation: "Get Global", operation: 'Get Global',
method: "GET", method: 'GET',
path: "/api/globals/{global-slug}", path: '/api/globals/{global-slug}',
description: "Get a global by slug", description: 'Get a global by slug',
example: { example: {
slug: "getGlobal", slug: 'getGlobal',
req: { req: {
credentials: true, credentials: true,
headers: true, headers: true,
}, },
res: { res: {
announcement: "Here is an announcement!", announcement: 'Here is an announcement!',
globalType: "announcement", globalType: 'announcement',
createdAt: "2023-04-28T08:53:56.066Z", createdAt: '2023-04-28T08:53:56.066Z',
updatedAt: "2023-04-28T08:53:56.066Z", updatedAt: '2023-04-28T08:53:56.066Z',
id: "644b89a496c64a833fe579c9", id: '644b89a496c64a833fe579c9',
}, },
}, },
}, },
{ {
operation: "Update Global", operation: 'Update Global',
method: "POST", method: 'POST',
path: "/api/globals/{global-slug}", path: '/api/globals/{global-slug}',
description: "Update a global by slug", description: 'Update a global by slug',
example: { example: {
slug: "updateGlobal", slug: 'updateGlobal',
req: { req: {
headers: true, headers: true,
credentials: true, credentials: true,
body: { body: {
announcement: "Paging Doctor Scrunt", announcement: 'Paging Doctor Scrunt',
}, },
}, },
res: { res: {
announcement: "Paging Doctor Scrunt", announcement: 'Paging Doctor Scrunt',
globalType: "announcement", globalType: 'announcement',
createdAt: "2023-04-28T08:53:56.066Z", createdAt: '2023-04-28T08:53:56.066Z',
updatedAt: "2023-04-28T08:53:56.066Z", updatedAt: '2023-04-28T08:53:56.066Z',
id: "644b89a496c64a833fe579c9", id: '644b89a496c64a833fe579c9',
}, },
}, },
}, },
@@ -491,65 +490,65 @@ In addition to the dynamically generated endpoints above Payload also has REST e
<RestExamples <RestExamples
data={[ data={[
{ {
operation: "Get Preference", operation: 'Get Preference',
method: "GET", method: 'GET',
path: "/api/payload-preferences/{key}", path: '/api/payload-preferences/{key}',
description: "Get a preference by key", description: 'Get a preference by key',
example: { example: {
slug: "getPreference", slug: 'getPreference',
req: { req: {
headers: true, headers: true,
credentials: true, credentials: true,
}, },
res: { res: {
_id: "644bb7a8307b3d363c6edf2c", _id: '644bb7a8307b3d363c6edf2c',
key: "region", key: 'region',
user: "644b8453cd20c7857da5a9b0", user: '644b8453cd20c7857da5a9b0',
userCollection: "users", userCollection: 'users',
__v: 0, __v: 0,
createdAt: "2023-04-28T12:10:16.689Z", createdAt: '2023-04-28T12:10:16.689Z',
updatedAt: "2023-04-28T12:10:16.689Z", updatedAt: '2023-04-28T12:10:16.689Z',
value: "Europe/London", value: 'Europe/London',
}, },
}, },
}, },
{ {
operation: "Create Preference", operation: 'Create Preference',
method: "POST", method: 'POST',
path: "/api/payload-preferences/{key}", path: '/api/payload-preferences/{key}',
description: "Create or update a preference by key", description: 'Create or update a preference by key',
example: { example: {
slug: "createPreference", slug: 'createPreference',
req: { req: {
headers: true, headers: true,
credentials: true, credentials: true,
body: { body: {
value: "Europe/London", value: 'Europe/London',
}, },
}, },
res: { res: {
message: "Updated successfully.", message: 'Updated successfully.',
doc: { doc: {
user: "644b8453cd20c7857da5a9b0", user: '644b8453cd20c7857da5a9b0',
key: "region", key: 'region',
userCollection: "users", userCollection: 'users',
value: "Europe/London", value: 'Europe/London',
}, },
}, },
}, },
}, },
{ {
operation: "Delete Preference", operation: 'Delete Preference',
method: "DELETE", method: 'DELETE',
path: "/api/payload-preferences/{key}", path: '/api/payload-preferences/{key}',
description: "Delete a preference by key", description: 'Delete a preference by key',
example: { example: {
slug: "deletePreference", slug: 'deletePreference',
req: { req: {
headers: true, headers: true,
}, },
res: { res: {
message: "deletedSuccessfully", message: 'deletedSuccessfully',
}, },
}, },
}, },
@@ -573,37 +572,36 @@ Each endpoint object needs to have:
Example: Example:
```ts ```ts
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from 'payload/types'
// a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking // a collection of 'orders' with an additional route for tracking details, reachable at /api/orders/:id/tracking
export const Orders: CollectionConfig = { export const Orders: CollectionConfig = {
slug: "orders", slug: 'orders',
fields: [ fields: [
/* ... */ /* ... */
], ],
// highlight-start // highlight-start
endpoints: [ endpoints: [
{ {
path: "/:id/tracking", path: '/:id/tracking',
method: "get", method: 'get',
handler: async (req, res, next) => { handler: async (req, res, next) => {
const tracking = await getTrackingInfo(req.params.id); const tracking = await getTrackingInfo(req.params.id)
if (tracking) { if (tracking) {
res.status(200).send({ tracking }); res.status(200).send({ tracking })
} else { } else {
res.status(404).send({ error: "not found" }); res.status(404).send({ error: 'not found' })
} }
}, },
}, },
], ],
// highlight-end // highlight-end
}; }
``` ```
<Banner> <Banner>
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
**req** will have the **payload** object and can be used inside your endpoint **req** will have the **payload** object and can be used inside your endpoint handlers for making
handlers for making calls like req.payload.find() that will make use of access calls like req.payload.find() that will make use of access control and hooks.
control and hooks.
</Banner> </Banner>

View File

@@ -12,7 +12,7 @@ keywords: admin, components, custom, customize, documentation, Content Managemen
This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve heck the following settings in your Payload config: This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve heck the following settings in your Payload config:
- CORS - If you are using the '*', try to explicitly only allow certain domains instead including the one you have specified. - CORS - If you are using the '\*', try to explicitly only allow certain domains instead including the one you have specified.
- CSRF - Do you have this set? if so, make sure your domain is whitelisted within the csrf domains. If not, probably not the issue, but probably can't hurt to whitelist it anyway. - CSRF - Do you have this set? if so, make sure your domain is whitelisted within the csrf domains. If not, probably not the issue, but probably can't hurt to whitelist it anyway.
- Cookie settings. If these are completely undefined, then that's fine. but if you have cookie domain set, or anything similar, make sure you don't have the domain misconfigured - Cookie settings. If these are completely undefined, then that's fine. but if you have cookie domain set, or anything similar, make sure you don't have the domain misconfigured

View File

@@ -102,19 +102,19 @@ By generating types, we'll end up with a file containing the following two TypeS
```ts ```ts
export interface User { export interface User {
id: string; id: string
name: string; name: string
email?: string; email?: string
resetPasswordToken?: string; resetPasswordToken?: string
resetPasswordExpiration?: string; resetPasswordExpiration?: string
loginAttempts?: number; loginAttempts?: number
lockUntil?: string; lockUntil?: string
} }
export interface Post { export interface Post {
id: string; id: string
title?: string; title?: string
author?: string | User; author?: string | User
} }
``` ```
@@ -145,25 +145,24 @@ will generate:
```ts ```ts
// a top level reusable interface!! // a top level reusable interface!!
export interface SharedMeta { export interface SharedMeta {
title?: string; title?: string
description?: string; description?: string
} }
// example usage inside collection interface // example usage inside collection interface
export interface Collection1 { export interface Collection1 {
// ...other fields // ...other fields
meta?: SharedMeta; meta?: SharedMeta
} }
``` ```
<Banner type="warning"> <Banner type="warning">
<strong>Naming Collisions</strong> <strong>Naming Collisions</strong>
<br /> <br />
Since these types are hoisted to the top level, you need to be aware that Since these types are hoisted to the top level, you need to be aware that naming collisions can
naming collisions can occur. For example, if you have a collection with the occur. For example, if you have a collection with the name of `Meta` and you also create a
name of `Meta` and you also create a interface with the name `Meta` they will interface with the name `Meta` they will collide. It is recommended to scope your interfaces by
collide. It is recommended to scope your interfaces by appending the field appending the field type to the end, i.e. `MetaGroup` or similar.
type to the end, i.e. `MetaGroup` or similar.
</Banner> </Banner>
### Using your types ### Using your types

View File

@@ -7,9 +7,8 @@ keywords: uploads, images, media, overview, documentation, Content Management Sy
--- ---
<Banner> <Banner>
Payload provides for everything you need to enable file upload, storage, and Payload provides for everything you need to enable file upload, storage, and management directly
management directly on your server—including extremely powerful file access on your server—including extremely powerful file access control.
control.
</Banner> </Banner>
![Upload admin panel functionality](https://payloadcms.com/images/docs/upload-admin.jpg) ![Upload admin panel functionality](https://payloadcms.com/images/docs/upload-admin.jpg)
@@ -34,31 +33,32 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
<Banner type="success"> <Banner type="success">
<strong>Tip:</strong> <strong>Tip:</strong>
<br />A common pattern is to create a <strong>Media</strong> collection and enable{" "} <br />A common pattern is to create a <strong>Media</strong> collection and enable <strong>
<strong>upload</strong> on that collection. upload
</strong> on that collection.
</Banner> </Banner>
#### Collection Upload Options #### Collection Upload Options
| Option | Description | | Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. | | **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. | | **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) | | **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) | | **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) | | **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) | | **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) | | **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) | | **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) | | **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. | | **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
_An asterisk denotes that a property above is required._ _An asterisk denotes that a property above is required._
**Example Upload collection:** **Example Upload collection:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Media: CollectionConfig = { export const Media: CollectionConfig = {
slug: 'media', slug: 'media',
@@ -98,7 +98,7 @@ export const Media: CollectionConfig = {
type: 'text', type: 'text',
}, },
], ],
}; }
``` ```
### Payload-wide Upload Options ### Payload-wide Upload Options
@@ -108,7 +108,7 @@ Payload relies on the [`express-fileupload`](https://www.npmjs.com/package/expre
A common example of what you might want to customize within Payload-wide Upload options would be to increase the allowed `fileSize` of uploads sent to Payload: A common example of what you might want to customize within Payload-wide Upload options would be to increase the allowed `fileSize` of uploads sent to Payload:
```ts ```ts
import { buildConfig } from 'payload/config'; import { buildConfig } from 'payload/config'
export default buildConfig({ export default buildConfig({
collections: [ collections: [
@@ -128,7 +128,7 @@ export default buildConfig({
fileSize: 5000000, // 5MB, written in bytes fileSize: 5000000, // 5MB, written in bytes
}, },
}, },
}); })
``` ```
### Image Sizes ### Image Sizes
@@ -152,11 +152,10 @@ If you are using a plugin to send your files off to a third-party file storage h
<Banner type="warning"> <Banner type="warning">
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
This is a fairly advanced feature. If you do disable local file storage, by This is a fairly advanced feature. If you do disable local file storage, by default, your admin
default, your admin panel's thumbnails will be broken as you will not have panel's thumbnails will be broken as you will not have stored a file. It will be totally up to you
stored a file. It will be totally up to you to use either a plugin or your own to use either a plugin or your own hooks to store your files in a permanent manner, as well as
hooks to store your files in a permanent manner, as well as provide your own provide your own admin thumbnail using <strong>upload.adminThumbnail</strong>.
admin thumbnail using <strong>upload.adminThumbnail</strong>.
</Banner> </Banner>
### Admin Thumbnails ### Admin Thumbnails
@@ -169,7 +168,7 @@ You can specify how Payload retrieves admin thumbnails for your upload-enabled C
**Example custom Admin thumbnail:** **Example custom Admin thumbnail:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Media: CollectionConfig = { export const Media: CollectionConfig = {
slug: 'media', slug: 'media',
@@ -180,17 +179,17 @@ export const Media: CollectionConfig = {
// ... image sizes here // ... image sizes here
], ],
// highlight-start // highlight-start
adminThumbnail: ({ doc }) => adminThumbnail: ({ doc }) => `https://google.com/custom-path-to-file/${doc.filename}`,
`https://google.com/custom-path-to-file/${doc.filename}`,
// highlight-end // highlight-end
}, },
}; }
``` ```
<Banner> <Banner>
<strong>Note:</strong> <strong>Note:</strong>
<br /> <br />
This function runs in the browser. If your function returns `null` or `false` Payload will show the default generic file thumbnail instead. This function runs in the browser. If your function returns `null` or `false` Payload will show
the default generic file thumbnail instead.
</Banner> </Banner>
### MimeTypes ### MimeTypes
@@ -202,7 +201,7 @@ Some example values are: `image/*`, `audio/*`, `video/*`, `image/png`, `applicat
**Example mimeTypes usage:** **Example mimeTypes usage:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Media: CollectionConfig = { export const Media: CollectionConfig = {
slug: 'media', slug: 'media',
@@ -211,7 +210,7 @@ export const Media: CollectionConfig = {
staticDir: 'media', staticDir: 'media',
mimeTypes: ['image/*', 'application/pdf'], // highlight-line mimeTypes: ['image/*', 'application/pdf'], // highlight-line
}, },
}; }
``` ```
### Uploading Files ### Uploading Files
@@ -219,9 +218,8 @@ export const Media: CollectionConfig = {
<Banner type="warning"> <Banner type="warning">
<strong>Important:</strong> <strong>Important:</strong>
<br /> <br />
Uploading files is currently only possible through the REST and Local APIs due Uploading files is currently only possible through the REST and Local APIs due to how GraphQL
to how GraphQL works. It's difficult and fairly nonsensical to support works. It's difficult and fairly nonsensical to support uploading files through GraphQL.
uploading files through GraphQL.
</Banner> </Banner>
To upload a file, use your collection's [`create`](/docs/rest-api/overview#collections) endpoint. Send it all the data that your Collection requires, as well as a `file` key containing the file that you'd like to upload. To upload a file, use your collection's [`create`](/docs/rest-api/overview#collections) endpoint. Send it all the data that your Collection requires, as well as a `file` key containing the file that you'd like to upload.

View File

@@ -9,24 +9,24 @@ keywords: version history, revisions, audit log, draft, publish, autosave, Conte
Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready. The Admin UI will automatically adapt to autosaving progress at an interval that you define, and will store all autosaved changes as a new Draft version. Never lose your work - and publish changes to the live document only when you're ready. Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can configure your collections and globals to autosave changes as drafts, and publish only you're ready. The Admin UI will automatically adapt to autosaving progress at an interval that you define, and will store all autosaved changes as a new Draft version. Never lose your work - and publish changes to the live document only when you're ready.
<Banner type="warning"> <Banner type="warning">
Autosave relies on Versions and Drafts being enabled in order to function. Autosave relies on Versions and Drafts being enabled in order to function.
</Banner> </Banner>
![Autosave Enabled](/images/docs/autosave-enabled.png) ![Autosave Enabled](/images/docs/autosave-enabled.png)
*If Autosave is enabled, drafts will be created automatically as the document is modified and the Admin UI adds an indicator describing when the document was last saved to the top right of the sidebar.* _If Autosave is enabled, drafts will be created automatically as the document is modified and the Admin UI adds an indicator describing when the document was last saved to the top right of the sidebar._
### Options ### Options
Collections and Globals both support the same options for configuring autosave. You can either set `versions.drafts.autosave` to `true`, or pass an object to configure autosave properties. Collections and Globals both support the same options for configuring autosave. You can either set `versions.drafts.autosave` to `true`, or pass an object to configure autosave properties.
| Drafts Autosave Options | Description | | Drafts Autosave Options | Description |
| ---------------------------- | -------------| | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interval` | Define an `interval` in milliseconds to automatically save progress while documents are edited. Document updates are "debounced" at this interval. Defaults to `2000`. | | `interval` | Define an `interval` in milliseconds to automatically save progress while documents are edited. Document updates are "debounced" at this interval. Defaults to `2000`. |
**Example config with versions, drafts, and autosave enabled:** **Example config with versions, drafts, and autosave enabled:**
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = { export const Pages: CollectionConfig = {
slug: 'pages', slug: 'pages',
@@ -34,7 +34,7 @@ export const Pages: CollectionConfig = {
read: ({ req }) => { read: ({ req }) => {
// If there is a user logged in, // If there is a user logged in,
// let them retrieve all documents // let them retrieve all documents
if (req.user) return true; if (req.user) return true
// If there is no user, // If there is no user,
// restrict the documents that are returned // restrict the documents that are returned
@@ -43,7 +43,7 @@ export const Pages: CollectionConfig = {
_status: { _status: {
equals: 'published', equals: 'published',
}, },
}; }
}, },
}, },
versions: { versions: {
@@ -54,7 +54,7 @@ export const Pages: CollectionConfig = {
// autosave: { // autosave: {
// interval: 1500, // interval: 1500,
// }, // },
} },
}, },
//.. the rest of the Pages config here //.. the rest of the Pages config here
} }
@@ -69,5 +69,7 @@ When `autosave` is enabled, all `update` operations within Payload expose a new
If we created a new version for each autosave, you'd quickly find a ton of autosaves that clutter up your `_versions` collection within the database. That would be messy quick because `autosave` is typically set to save a document every ~2000ms or so. If we created a new version for each autosave, you'd quickly find a ton of autosaves that clutter up your `_versions` collection within the database. That would be messy quick because `autosave` is typically set to save a document every ~2000ms or so.
<Banner type="success"> <Banner type="success">
Instead of creating a new version each time a document is autosaved, Payload smartly only creates <strong>one</strong> autosave version, and then updates that specific version with each autosave performed. This makes sure that your versions remain nice and tidy. Instead of creating a new version each time a document is autosaved, Payload smartly only creates{' '}
<strong>one</strong> autosave version, and then updates that specific version with each autosave
performed. This makes sure that your versions remain nice and tidy.
</Banner> </Banner>

View File

@@ -8,22 +8,20 @@ keywords: version history, drafts, preview, draft, restore, publish, autosave, C
Payload's Draft functionality builds on top of the Versions functionality to allow you to make changes to your collection documents and globals, but publish only when you're ready. This functionality allows you to build powerful Preview environments for your data, where you can make sure your changes look good before publishing documents. Payload's Draft functionality builds on top of the Versions functionality to allow you to make changes to your collection documents and globals, but publish only when you're ready. This functionality allows you to build powerful Preview environments for your data, where you can make sure your changes look good before publishing documents.
<Banner type="warning"> <Banner type="warning">Drafts rely on Versions being enabled in order to function.</Banner>
Drafts rely on Versions being enabled in order to function.
</Banner>
By enabling Versions with Drafts, your collections and globals can maintain _newer_, and _unpublished_ versions of your documents. It's perfect for cases where you might want to work on a document, update it and save your progress, but not necessarily make it publicly published right away. Drafts are extremely helpful when building preview implementations. By enabling Versions with Drafts, your collections and globals can maintain _newer_, and _unpublished_ versions of your documents. It's perfect for cases where you might want to work on a document, update it and save your progress, but not necessarily make it publicly published right away. Drafts are extremely helpful when building preview implementations.
![Drafts Enabled](/images/docs/drafts-enabled.png) ![Drafts Enabled](/images/docs/drafts-enabled.png)
*If Drafts are enabled, the typical Save button is replaced with new actions which allow you to either save a draft, or publish your changes.* _If Drafts are enabled, the typical Save button is replaced with new actions which allow you to either save a draft, or publish your changes._
### Options ### Options
Collections and Globals both support the same options for configuring drafts. You can either set `versions.drafts` to `true`, or pass an object to configure draft properties. Collections and Globals both support the same options for configuring drafts. You can either set `versions.drafts` to `true`, or pass an object to configure draft properties.
| Draft Option | Description | | Draft Option | Description |
| ---------------------------- | -------------| | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autosave` | Enable `autosave` to automatically save progress while documents are edited. To enable, set to `true` or pass an object with [options](/docs/versions/autosave). | | `autosave` | Enable `autosave` to automatically save progress while documents are edited. To enable, set to `true` or pass an object with [options](/docs/versions/autosave). |
### Database changes ### Database changes
@@ -33,14 +31,18 @@ By enabling drafts on a collection or a global, Payload will <strong>automatical
Within the Admin UI, if drafts are enabled, a document can be shown with one of three "statuses": Within the Admin UI, if drafts are enabled, a document can be shown with one of three "statuses":
1. <strong>Draft</strong> - if a document has never been published, and only draft versions of the document are present 1. <strong>Draft</strong> - if a document has never been published, and only draft versions of the document
are present
1. <strong>Published</strong> - if a document is published and there are no newer drafts available 1. <strong>Published</strong> - if a document is published and there are no newer drafts available
1. <strong>Changed</strong> - if a document has been published, but there are newer drafts available and not yet published 1. <strong>Changed</strong> - if a document has been published, but there are newer drafts available
and not yet published
### Draft API ### Draft API
<Banner type="success"> <Banner type="success">
If drafts are enabled on your collection or global, important and powerful changes are made to your REST, GraphQL, and Local APIs that allow you to specify if you are interacting with drafts or with live documents. If drafts are enabled on your collection or global, important and powerful changes are made to
your REST, GraphQL, and Local APIs that allow you to specify if you are interacting with drafts or
with live documents.
</Banner> </Banner>
##### Updating or creating drafts ##### Updating or creating drafts
@@ -72,7 +74,9 @@ But, if you specify `draft` as `true`, Payload will automatically replace your p
### Controlling who can see Collection drafts ### Controlling who can see Collection drafts
<Banner type="warning"> <Banner type="warning">
If you're using the <strong>drafts</strong> feature, it's important for you to consider who can view your drafts, and who can view only published documents. Luckily, Payload makes this extremely simple and puts the power completely in your hands. If you're using the <strong>drafts</strong> feature, it's important for you to consider who can
view your drafts, and who can view only published documents. Luckily, Payload makes this extremely
simple and puts the power completely in your hands.
</Banner> </Banner>
##### Restricting draft access ##### Restricting draft access
@@ -82,7 +86,7 @@ You can use the `read` [Access Control](/docs/access-control/collections#read) m
Here is an example that utilizes the `_status` field to require a user to be logged in to retrieve drafts: Here is an example that utilizes the `_status` field to require a user to be logged in to retrieve drafts:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = { export const Pages: CollectionConfig = {
slug: 'pages', slug: 'pages',
@@ -90,7 +94,7 @@ export const Pages: CollectionConfig = {
read: ({ req }) => { read: ({ req }) => {
// If there is a user logged in, // If there is a user logged in,
// let them retrieve all documents // let them retrieve all documents
if (req.user) return true; if (req.user) return true
// If there is no user, // If there is no user,
// restrict the documents that are returned // restrict the documents that are returned
@@ -99,25 +103,32 @@ export const Pages: CollectionConfig = {
_status: { _status: {
equals: 'published', equals: 'published',
}, },
}; }
}, },
}, },
versions: { versions: {
drafts: true drafts: true,
}, },
//.. the rest of the Pages config here //.. the rest of the Pages config here
} }
``` ```
<Banner type="warning"> <Banner type="warning">
<strong>Note regarding adding versions to an existing collection</strong><br/> <strong>Note regarding adding versions to an existing collection</strong>
If you already have a collection with documents, and you <em>opt in</em> to draft functionality after you have already created existing documents, all of your old documents <em>will not have a _status field</em> until you resave them. For this reason, if you are <em>adding</em> versions into an existing collection, you might want to write your access control function to allow for users to read both documents where <strong>_status is equal to "published"</strong> as well as where <strong>_status does not exist</strong>. <br />
If you already have a collection with documents, and you <em>opt in</em> to draft functionality
after you have already created existing documents, all of your old documents{' '}
<em>will not have a _status field</em> until you resave them. For this reason, if you are{' '}
<em>adding</em> versions into an existing collection, you might want to write your access control
function to allow for users to read both documents where{' '}
<strong>_status is equal to "published"</strong> as well as where{' '}
<strong>_status does not exist</strong>.
</Banner> </Banner>
Here is an example for how to write an access control function that grants access to both documents where `_status` is equal to "published" and where `_status` does not exist: Here is an example for how to write an access control function that grants access to both documents where `_status` is equal to "published" and where `_status` does not exist:
```ts ```ts
import { CollectionConfig } from 'payload/types'; import { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = { export const Pages: CollectionConfig = {
slug: 'pages', slug: 'pages',
@@ -125,7 +136,7 @@ export const Pages: CollectionConfig = {
read: ({ req }) => { read: ({ req }) => {
// If there is a user logged in, // If there is a user logged in,
// let them retrieve all documents // let them retrieve all documents
if (req.user) return true; if (req.user) return true
// If there is no user, // If there is no user,
// restrict the documents that are returned // restrict the documents that are returned
@@ -141,14 +152,14 @@ export const Pages: CollectionConfig = {
{ {
_status: { _status: {
exists: false, exists: false,
} },
} },
] ],
}; }
}, },
}, },
versions: { versions: {
drafts: true drafts: true,
}, },
//.. the rest of the Pages config here //.. the rest of the Pages config here
} }
@@ -161,4 +172,3 @@ If a document is published, the Payload Admin UI will be updated to show an "unp
### Reverting to published ### Reverting to published
If a document is published, and you have made further changes which are saved as a draft, Payload will show a "revert to published" button at the top of the sidebar which will allow you to reject your draft changes and "revert" back to the published state of the document. Your drafts will still be saved, but a new version will be created that will reflect the last published state of the document. If a document is published, and you have made further changes which are saved as a draft, Payload will show a "revert to published" button at the top of the sidebar which will allow you to reject your draft changes and "revert" back to the published state of the document. Your drafts will still be saved, but a new version will be created that will reflect the last published state of the document.

View File

@@ -7,8 +7,8 @@ keywords: version history, revisions, audit log, draft, publish, restore, autosa
--- ---
<Banner> <Banner>
Payload's powerful Versions functionality allows you to keep a running history Payload's powerful Versions functionality allows you to keep a running history of changes over
of changes over time and extensible to fit any content publishing workflow. time and extensible to fit any content publishing workflow.
</Banner> </Banner>
When enabled, Payload will automatically scaffold a new Collection in your database to store versions of your document(s) over time, and the Admin UI will be extended with additional views that allow you to browse document versions, view diffs in order to see exactly what has changed in your documents (and when they changed), and restore documents back to prior versions easily. When enabled, Payload will automatically scaffold a new Collection in your database to store versions of your document(s) over time, and the Admin UI will be extended with additional views that allow you to browse document versions, view diffs in order to see exactly what has changed in your documents (and when they changed), and restore documents back to prior versions easily.
@@ -26,9 +26,9 @@ _Comparing an old version to a newer version of a document_
- Build a powerful publishing schedule mechanism to create documents and have them become publicly readable automatically at a future date - Build a powerful publishing schedule mechanism to create documents and have them become publicly readable automatically at a future date
<Banner type="success"> <Banner type="success">
Versions are extremely performant and totally opt-in. They don't change the Versions are extremely performant and totally opt-in. They don't change the shape of your data at
shape of your data at all. All versions are stored in a separate Collection all. All versions are stored in a separate Collection and can be turned on and off easily at your
and can be turned on and off easily at your discretion. discretion.
</Banner> </Banner>
### Options ### Options
@@ -126,18 +126,18 @@ Versions expose new operations for both collections and globals. They allow you
// Result will be a paginated set of Versions. // Result will be a paginated set of Versions.
// See /docs/queries/pagination for more. // See /docs/queries/pagination for more.
const result = await payload.findVersions({ const result = await payload.findVersions({
collection: "posts", // required collection: 'posts', // required
depth: 2, depth: 2,
page: 1, page: 1,
limit: 10, limit: 10,
where: {}, // pass a `where` query here where: {}, // pass a `where` query here
sort: "-createdAt", sort: '-createdAt',
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Find by ID #### Find by ID
@@ -145,15 +145,15 @@ const result = await payload.findVersions({
```js ```js
// Result will be a Post document. // Result will be a Post document.
const result = await payload.findVersionByID({ const result = await payload.findVersionByID({
collection: "posts", // required collection: 'posts', // required
id: "507f1f77bcf86cd799439013", // required id: '507f1f77bcf86cd799439013', // required
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Restore #### Restore
@@ -161,13 +161,13 @@ const result = await payload.findVersionByID({
```js ```js
// Result will be the restored global document. // Result will be the restored global document.
const result = await payload.restoreVersion({ const result = await payload.restoreVersion({
collection: "posts", // required collection: 'posts', // required
id: "507f1f77bcf86cd799439013", // required id: '507f1f77bcf86cd799439013', // required
depth: 2, depth: 2,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
**Global REST endpoints:** **Global REST endpoints:**
@@ -199,18 +199,18 @@ const result = await payload.restoreVersion({
// Result will be a paginated set of Versions. // Result will be a paginated set of Versions.
// See /docs/queries/pagination for more. // See /docs/queries/pagination for more.
const result = await payload.findGlobalVersions({ const result = await payload.findGlobalVersions({
slug: "header", // required slug: 'header', // required
depth: 2, depth: 2,
page: 1, page: 1,
limit: 10, limit: 10,
where: {}, // pass a `where` query here where: {}, // pass a `where` query here
sort: "-createdAt", sort: '-createdAt',
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Find by ID #### Find by ID
@@ -218,15 +218,15 @@ const result = await payload.findGlobalVersions({
```js ```js
// Result will be a Post document. // Result will be a Post document.
const result = await payload.findGlobalVersionByID({ const result = await payload.findGlobalVersionByID({
slug: "header", // required slug: 'header', // required
id: "507f1f77bcf86cd799439013", // required id: '507f1f77bcf86cd799439013', // required
depth: 2, depth: 2,
locale: "en", locale: 'en',
fallbackLocale: false, fallbackLocale: false,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
#### Restore #### Restore
@@ -234,13 +234,13 @@ const result = await payload.findGlobalVersionByID({
```js ```js
// Result will be the restored global document. // Result will be the restored global document.
const result = await payload.restoreGlobalVersion({ const result = await payload.restoreGlobalVersion({
slug: "header", // required slug: 'header', // required
id: "507f1f77bcf86cd799439013", // required id: '507f1f77bcf86cd799439013', // required
depth: 2, depth: 2,
user: dummyUser, user: dummyUser,
overrideAccess: false, overrideAccess: false,
showHiddenFields: true, showHiddenFields: true,
}); })
``` ```
### Access Control ### Access Control

View File

@@ -1,70 +1,43 @@
{ {
"name": "payload-monorepo",
"private": true,
"version": "0.0.0",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"engines": {
"node": ">=14",
"pnpm": ">=8"
},
"author": { "author": {
"email": "info@payloadcms.com", "email": "info@payloadcms.com",
"name": "Payload", "name": "Payload",
"url": "https://payloadcms.com" "url": "https://payloadcms.com"
}, },
"maintainers": [
{
"name": "Payload",
"email": "info@payloadcms.com",
"url": "https://payloadcms.com"
}
],
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git"
},
"homepage": "https://payloadcms.com",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"sideEffects": false,
"bin": { "bin": {
"payload": "bin.js" "payload": "bin.js"
}, },
"workspaces:": [
"packages/*"
],
"scripts": {
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"build:tsc": "tsc",
"build:components": "webpack --config dist/bundlers/webpack/components.config.js",
"build": "pnpm copyfiles && pnpm build:tsc && pnpm build:components",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"pnpm build:tsc\"",
"dev": "pnpm --filter payload run dev",
"dev:postgres": "pnpm --filter payload run dev:postgres",
"dev:generate-types": "pnpm --filter payload run dev:generate-types",
"dev:generate-graphql-schema": "pnpm --filter payload run dev:generate-graphql-schema",
"pretest": "pnpm build",
"test": "pnpm --filter payload run test",
"test:int": "pnpm --filter payload run test:int",
"test:e2e": "pnpm --filter payload run test:e2e",
"test:e2e:headed": "pnpm --filter payload run test:e2e:headed",
"test:e2e:debug": "pnpm --filter payload run test:e2e:debug",
"test:components": "pnpm --filter payload run test:components",
"translateNewKeys": "pnpm --filter payload run translateNewKeys",
"clean:cache": "rimraf node_modules/.cache && rimraf packages/payload/node_modules/.cache",
"clean": "rimraf dist && rimraf packages/payload/dist",
"release:patch": "release-it patch",
"release:minor": "release-it minor",
"release:major": "release-it major",
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.json",
"release:canary": "release-it pre --preReleaseId=canary --npm.tag=canary --config .release-it.pre.json",
"fix": "eslint \"src/**/*.ts\" --fix",
"lint": "eslint \"src/**/*.ts\""
},
"bugs": { "bugs": {
"url": "https://github.com/payloadcms/payload" "url": "https://github.com/payloadcms/payload"
}, },
"dependencies": {
"turbo": "^1.10.13"
},
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/node": "20.5.7",
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"prettier": "^3.0.3",
"typescript": "5.2.2"
},
"engines": {
"node": ">=14",
"pnpm": ">=8"
},
"files": [
"bin.js",
"dist",
"docs",
"components",
"scss",
"*.js",
"*.d.ts",
"!jest.config.js",
"!jest.components.config.js"
],
"homepage": "https://payloadcms.com",
"keywords": [ "keywords": [
"payload", "payload",
"cms", "cms",
@@ -86,29 +59,56 @@
"react", "react",
"auth" "auth"
], ],
"dependencies": { "license": "MIT",
"turbo": "^1.10.13" "main": "./dist/index.js",
}, "maintainers": [
"devDependencies": { {
"@payloadcms/eslint-config": "workspace:*", "email": "info@payloadcms.com",
"@types/node": "20.5.7", "name": "Payload",
"copyfiles": "2.4.1", "url": "https://payloadcms.com"
"cross-env": "7.0.3", }
"prettier": "^3.0.3",
"typescript": "5.2.2"
},
"files": [
"bin.js",
"dist",
"docs",
"components",
"scss",
"*.js",
"*.d.ts",
"!jest.config.js",
"!jest.components.config.js"
], ],
"name": "payload-monorepo",
"private": true,
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org/" "registry": "https://registry.npmjs.org/"
} },
"repository": {
"type": "git",
"url": "https://github.com/payloadcms/payload.git"
},
"scripts": {
"build": "pnpm copyfiles && pnpm build:tsc && pnpm build:components",
"build:components": "webpack --config dist/bundlers/webpack/components.config.js",
"build:tsc": "tsc",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"pnpm build:tsc\"",
"clean": "rimraf dist && rimraf packages/payload/dist",
"clean:cache": "rimraf node_modules/.cache && rimraf packages/payload/node_modules/.cache",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"dev": "pnpm --filter payload run dev",
"dev:generate-graphql-schema": "pnpm --filter payload run dev:generate-graphql-schema",
"dev:generate-types": "pnpm --filter payload run dev:generate-types",
"dev:postgres": "pnpm --filter payload run dev:postgres",
"fix": "eslint \"src/**/*.ts\" --fix",
"lint": "eslint \"src/**/*.ts\"",
"pretest": "pnpm build",
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.json",
"release:canary": "release-it pre --preReleaseId=canary --npm.tag=canary --config .release-it.pre.json",
"release:major": "release-it major",
"release:minor": "release-it minor",
"release:patch": "release-it patch",
"test": "pnpm --filter payload run test",
"test:components": "pnpm --filter payload run test:components",
"test:e2e": "pnpm --filter payload run test:e2e",
"test:e2e:debug": "pnpm --filter payload run test:e2e:debug",
"test:e2e:headed": "pnpm --filter payload run test:e2e:headed",
"test:int": "pnpm --filter payload run test:int",
"translateNewKeys": "pnpm --filter payload run translateNewKeys"
},
"sideEffects": false,
"typings": "./dist/index.d.ts",
"version": "0.0.0",
"workspaces:": [
"packages/*"
]
} }

View File

@@ -4,7 +4,7 @@ module.exports = {
overrides: [ overrides: [
{ {
extends: ['plugin:@typescript-eslint/disable-type-checked'], extends: ['plugin:@typescript-eslint/disable-type-checked'],
files: ['*.js', '*.cjs'], files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
}, },
], ],
parserOptions: { parserOptions: {

View File

@@ -1,38 +1,5 @@
{ {
"name": "@payloadcms/db-mongodb",
"version": "0.0.1",
"description": "The officially supported MongoDB database adapter for Payload",
"main": "./src/index.ts",
"types": "./src/index.ts",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.js",
"default": "./dist/index.js"
}
}
},
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"repository": "https://github.com/payloadcms/payload",
"author": "Payload CMS, Inc.", "author": "Payload CMS, Inc.",
"license": "MIT",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
"mongodb-memory-server": "8.13.0",
"payload": "workspace:*",
"@payloadcms/eslint-config": "workspace:*"
},
"dependencies": { "dependencies": {
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
@@ -41,5 +8,38 @@
"mongoose-aggregate-paginate-v2": "1.0.6", "mongoose-aggregate-paginate-v2": "1.0.6",
"mongoose-paginate-v2": "1.7.22", "mongoose-paginate-v2": "1.7.22",
"uuid": "9.0.0" "uuid": "9.0.0"
} },
"description": "The officially supported MongoDB database adapter for Payload",
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/mongoose-aggregate-paginate-v2": "1.0.9",
"mongodb-memory-server": "8.13.0",
"payload": "workspace:*"
},
"exports": {
".": {
"default": "./src/index.ts",
"types": "./src/index.ts"
}
},
"license": "MIT",
"main": "./src/index.ts",
"name": "@payloadcms/db-mongodb",
"publishConfig": {
"exports": {
".": {
"default": "./dist/index.js",
"types": "./dist/index.js"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"
},
"repository": "https://github.com/payloadcms/payload",
"scripts": {
"build": "tsc"
},
"types": "./src/index.ts",
"version": "0.0.1"
} }

View File

@@ -1,67 +1,61 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import type { ConnectOptions } from 'mongoose'; import type { ConnectOptions } from 'mongoose'
import mongoose from 'mongoose'; import type { Connect } from 'payload/database'
import type { Connect } from 'payload/database';
import type { MongooseAdapter } from '.';
export const connect: Connect = async function connect( import mongoose from 'mongoose'
this: MongooseAdapter,
payload, import type { MongooseAdapter } from '.'
) {
export const connect: Connect = async function connect(this: MongooseAdapter, payload) {
if (this.url === false) { if (this.url === false) {
return; return
} }
if (!payload.local && typeof this.url !== 'string') { if (!payload.local && typeof this.url !== 'string') {
throw new Error('Error: missing MongoDB connection URL.'); throw new Error('Error: missing MongoDB connection URL.')
} }
let urlToConnect = this.url; let urlToConnect = this.url
let successfulConnectionMessage = 'Connected to MongoDB server successfully!'; let successfulConnectionMessage = 'Connected to MongoDB server successfully!'
const connectionOptions: ConnectOptions & { useFacet: undefined } = { const connectionOptions: ConnectOptions & { useFacet: undefined } = {
autoIndex: true, autoIndex: true,
...this.connectOptions, ...this.connectOptions,
useFacet: undefined, useFacet: undefined,
}; }
if (process.env.NODE_ENV === 'test') { if (process.env.NODE_ENV === 'test') {
if (process.env.PAYLOAD_TEST_MONGO_URL) { if (process.env.PAYLOAD_TEST_MONGO_URL) {
urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL; urlToConnect = process.env.PAYLOAD_TEST_MONGO_URL
} else { } else {
connectionOptions.dbName = 'payloadmemory'; connectionOptions.dbName = 'payloadmemory'
const { MongoMemoryServer } = require('mongodb-memory-server'); const { MongoMemoryServer } = require('mongodb-memory-server')
const getPort = require('get-port'); const getPort = require('get-port')
const port = await getPort(); const port = await getPort()
this.mongoMemoryServer = await MongoMemoryServer.create({ this.mongoMemoryServer = await MongoMemoryServer.create({
instance: { instance: {
dbName: 'payloadmemory', dbName: 'payloadmemory',
port, port,
}, },
}); })
urlToConnect = this.mongoMemoryServer.getUri(); urlToConnect = this.mongoMemoryServer.getUri()
successfulConnectionMessage = 'Connected to in-memory MongoDB server successfully!'; successfulConnectionMessage = 'Connected to in-memory MongoDB server successfully!'
} }
} }
try { try {
this.connection = ( this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
await mongoose.connect(urlToConnect, connectionOptions)
).connection;
if (process.env.PAYLOAD_DROP_DATABASE === 'true') { if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----'); this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase(); await mongoose.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----'); this.payload.logger.info('---- DROPPED DATABASE ----')
} }
this.payload.logger.info(successfulConnectionMessage); this.payload.logger.info(successfulConnectionMessage)
} catch (err) { } catch (err) {
this.payload.logger.error( this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
`Error: cannot connect to MongoDB. Details: ${err.message}`, process.exit(1)
err,
);
process.exit(1);
} }
}; }

View File

@@ -1,27 +1,29 @@
import { Create } from 'payload/database'; import type { Create } from 'payload/database'
import type { Document } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types'; import type { Document } from 'payload/types'
import type { MongooseAdapter } from '.';
import { withSession } from './withSession'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession'
export const create: Create = async function create( export const create: Create = async function create(
this: MongooseAdapter, this: MongooseAdapter,
{ collection, data, req = {} as PayloadRequest }, { collection, data, req = {} as PayloadRequest },
) { ) {
const Model = this.collections[collection]; const Model = this.collections[collection]
const options = withSession(this, req.transactionID); const options = withSession(this, req.transactionID)
const [doc] = await Model.create([data], options); const [doc] = await Model.create([data], options)
// doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here // doc.toJSON does not do stuff like converting ObjectIds to string, or date strings to date objects. That's why we use JSON.parse/stringify here
const result: Document = JSON.parse(JSON.stringify(doc)); const result: Document = JSON.parse(JSON.stringify(doc))
const verificationToken = doc._verificationToken; const verificationToken = doc._verificationToken
// custom id type reset // custom id type reset
result.id = result._id; result.id = result._id
if (verificationToken) { if (verificationToken) {
result._verificationToken = verificationToken; result._verificationToken = verificationToken
} }
return result; return result
}; }

View File

@@ -1,27 +1,29 @@
import { PayloadRequest } from 'payload/types'; import type { CreateGlobal } from 'payload/database'
import { CreateGlobal } from 'payload/database'; import type { PayloadRequest } from 'payload/types'
import sanitizeInternalFields from './utilities/sanitizeInternalFields';
import { withSession } from './withSession'; import type { MongooseAdapter } from '.'
import type { MongooseAdapter } from '.';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const createGlobal: CreateGlobal = async function createGlobal( export const createGlobal: CreateGlobal = async function createGlobal(
this: MongooseAdapter, this: MongooseAdapter,
{ data, slug, req = {} as PayloadRequest }, { data, req = {} as PayloadRequest, slug },
) { ) {
const Model = this.globals; const Model = this.globals
const global = { const global = {
globalType: slug, globalType: slug,
...data, ...data,
}; }
const options = withSession(this, req.transactionID); const options = withSession(this, req.transactionID)
let [result] = (await Model.create([global], options)) as any; let [result] = (await Model.create([global], options)) as any
result = JSON.parse(JSON.stringify(result)); result = JSON.parse(JSON.stringify(result))
// custom id type reset // custom id type reset
result.id = result._id; result.id = result._id
result = sanitizeInternalFields(result); result = sanitizeInternalFields(result)
return result; return result
}; }

View File

@@ -1,45 +1,47 @@
import type { CreateVersion } from 'payload/database'; import type { CreateVersion } from 'payload/database'
import { PayloadRequest } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import type { Document } from 'payload/types'; import type { Document } from 'payload/types'
import type { MongooseAdapter } from '.';
import { withSession } from './withSession'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession'
export const createVersion: CreateVersion = async function createVersion( export const createVersion: CreateVersion = async function createVersion(
this: MongooseAdapter, this: MongooseAdapter,
{ {
collectionSlug,
parent,
versionData,
autosave, autosave,
collectionSlug,
createdAt, createdAt,
updatedAt, parent,
req = {} as PayloadRequest, req = {} as PayloadRequest,
updatedAt,
versionData,
}, },
) { ) {
const VersionModel = this.versions[collectionSlug]; const VersionModel = this.versions[collectionSlug]
const options = withSession(this, req.transactionID); const options = withSession(this, req.transactionID)
const [doc] = await VersionModel.create( const [doc] = await VersionModel.create(
[ [
{ {
parent,
version: versionData,
autosave, autosave,
createdAt, createdAt,
parent,
updatedAt, updatedAt,
version: versionData,
}, },
], ],
options, options,
req, req,
); )
const result: Document = JSON.parse(JSON.stringify(doc)); const result: Document = JSON.parse(JSON.stringify(doc))
const verificationToken = doc._verificationToken; const verificationToken = doc._verificationToken
// custom id type reset // custom id type reset
result.id = result._id; result.id = result._id
if (verificationToken) { if (verificationToken) {
result._verificationToken = verificationToken; result._verificationToken = verificationToken
} }
return result; return result
}; }

View File

@@ -1,20 +1,24 @@
import { DeleteMany } from 'payload/database'; import type { DeleteMany } from 'payload/database'
import { PayloadRequest } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import type { MongooseAdapter } from '.';
import { withSession } from './withSession';
export const deleteMany: DeleteMany = async function deleteMany(this: MongooseAdapter, import type { MongooseAdapter } from '.'
{ collection, where, req = {} as PayloadRequest }) {
const Model = this.collections[collection]; import { withSession } from './withSession'
export const deleteMany: DeleteMany = async function deleteMany(
this: MongooseAdapter,
{ collection, req = {} as PayloadRequest, where },
) {
const Model = this.collections[collection]
const options = { const options = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
lean: true, lean: true,
}; }
const query = await Model.buildQuery({ const query = await Model.buildQuery({
payload: this.payload, payload: this.payload,
where, where,
}); })
await Model.deleteMany(query, options); await Model.deleteMany(query, options)
}; }

View File

@@ -1,29 +1,31 @@
import { DeleteOne } from 'payload/database'; import type { DeleteOne } from 'payload/database'
import type { Document } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types'; import type { Document } from 'payload/types'
import sanitizeInternalFields from './utilities/sanitizeInternalFields';
import type { MongooseAdapter } from '.'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const deleteOne: DeleteOne = async function deleteOne( export const deleteOne: DeleteOne = async function deleteOne(
this: MongooseAdapter, this: MongooseAdapter,
{ collection, where, req = {} as PayloadRequest }, { collection, req = {} as PayloadRequest, where },
) { ) {
const Model = this.collections[collection]; const Model = this.collections[collection]
const options = withSession(this, req.transactionID); const options = withSession(this, req.transactionID)
const query = await Model.buildQuery({ const query = await Model.buildQuery({
payload: this.payload, payload: this.payload,
where, where,
}); })
const doc = await Model.findOneAndDelete(query, options).lean(); const doc = await Model.findOneAndDelete(query, options).lean()
let result: Document = JSON.parse(JSON.stringify(doc)); let result: Document = JSON.parse(JSON.stringify(doc))
// custom id type reset // custom id type reset
result.id = result._id; result.id = result._id
result = sanitizeInternalFields(result); result = sanitizeInternalFields(result)
return result; return result
}; }

View File

@@ -1,21 +1,25 @@
import { DeleteVersions } from 'payload/database'; import type { DeleteVersions } from 'payload/database'
import { PayloadRequest } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import type { MongooseAdapter } from '.';
import { withSession } from './withSession';
export const deleteVersions: DeleteVersions = async function deleteVersions(this: MongooseAdapter, import type { MongooseAdapter } from '.'
{ collection, where, locale, req = {} as PayloadRequest }) {
const VersionsModel = this.versions[collection]; import { withSession } from './withSession'
export const deleteVersions: DeleteVersions = async function deleteVersions(
this: MongooseAdapter,
{ collection, locale, req = {} as PayloadRequest, where },
) {
const VersionsModel = this.versions[collection]
const options = { const options = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
lean: true, lean: true,
}; }
const query = await VersionsModel.buildQuery({ const query = await VersionsModel.buildQuery({
payload: this.payload,
locale, locale,
payload: this.payload,
where, where,
}); })
await VersionsModel.deleteMany(query, options); await VersionsModel.deleteMany(query, options)
}; }

View File

@@ -1,13 +1,13 @@
import mongoose from 'mongoose'; import type { Destroy } from 'payload/database'
import { Destroy } from 'payload/database';
import { MongooseAdapter } from './index';
export const destroy: Destroy = async function destroy( import mongoose from 'mongoose'
this: MongooseAdapter,
) { import type { MongooseAdapter } from './index'
export const destroy: Destroy = async function destroy(this: MongooseAdapter) {
if (this.mongoMemoryServer) { if (this.mongoMemoryServer) {
await mongoose.connection.dropDatabase(); await mongoose.connection.dropDatabase()
await mongoose.connection.close(); await mongoose.connection.close()
await this.mongoMemoryServer.stop(); await this.mongoMemoryServer.stop()
} }
}; }

View File

@@ -1,79 +1,73 @@
import type { PaginateOptions } from 'mongoose'; import type { PaginateOptions } from 'mongoose'
import type { Find } from 'payload/database'; import type { Find } from 'payload/database'
import { flattenWhereToOperators } from 'payload/database'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'; import { flattenWhereToOperators } from 'payload/database'
import { buildSortParam } from './queries/buildSortParam';
import type { MongooseAdapter } from '.'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession';
import { buildSortParam } from './queries/buildSortParam'
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const find: Find = async function find( export const find: Find = async function find(
this: MongooseAdapter, this: MongooseAdapter,
{ { collection, limit, locale, page, pagination, req = {} as PayloadRequest, sort: sortArg, where },
collection,
where,
page,
limit,
sort: sortArg,
locale,
pagination,
req = {} as PayloadRequest,
},
) { ) {
const Model = this.collections[collection]; const Model = this.collections[collection]
const collectionConfig = this.payload.collections[collection].config; const collectionConfig = this.payload.collections[collection].config
const options = withSession(this, req.transactionID); const options = withSession(this, req.transactionID)
let hasNearConstraint = false; let hasNearConstraint = false
if (where) { if (where) {
const constraints = flattenWhereToOperators(where); const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near')); hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
} }
let sort; let sort
if (!hasNearConstraint) { if (!hasNearConstraint) {
sort = buildSortParam({ sort = buildSortParam({
sort: sortArg || collectionConfig.defaultSort,
fields: collectionConfig.fields,
timestamps: true,
config: this.payload.config, config: this.payload.config,
fields: collectionConfig.fields,
locale, locale,
}); sort: sortArg || collectionConfig.defaultSort,
timestamps: true,
})
} }
const query = await Model.buildQuery({ const query = await Model.buildQuery({
payload: this.payload,
locale, locale,
payload: this.payload,
where, where,
}); })
const paginationOptions: PaginateOptions = { const paginationOptions: PaginateOptions = {
page, forceCountFn: hasNearConstraint,
sort,
lean: true, lean: true,
leanWithId: true, leanWithId: true,
useEstimatedCount: hasNearConstraint,
forceCountFn: hasNearConstraint,
pagination,
options, options,
}; page,
pagination,
if (limit > 0) { sort,
paginationOptions.limit = limit; useEstimatedCount: hasNearConstraint,
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit;
} }
const result = await Model.paginate(query, paginationOptions); if (limit > 0) {
const docs = JSON.parse(JSON.stringify(result.docs)); paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
}
const result = await Model.paginate(query, paginationOptions)
const docs = JSON.parse(JSON.stringify(result.docs))
return { return {
...result, ...result,
docs: docs.map((doc) => { docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
doc.id = doc._id; doc.id = doc._id
return sanitizeInternalFields(doc); return sanitizeInternalFields(doc)
}), }),
}; }
}; }

View File

@@ -1,39 +1,42 @@
import { combineQueries } from 'payload/database'; import type { FindGlobal } from 'payload/database'
import type { FindGlobal } from 'payload/database'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'; import { combineQueries } from 'payload/database'
import type { MongooseAdapter } from '.';
import { withSession } from './withSession'; import type { MongooseAdapter } from '.'
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const findGlobal: FindGlobal = async function findGlobal( export const findGlobal: FindGlobal = async function findGlobal(
this: MongooseAdapter, this: MongooseAdapter,
{ slug, locale, where, req = {} as PayloadRequest }, { locale, req = {} as PayloadRequest, slug, where },
) { ) {
const Model = this.globals; const Model = this.globals
const options = { const options = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
lean: true, lean: true,
}; }
const query = await Model.buildQuery({ const query = await Model.buildQuery({
where: combineQueries({ globalType: { equals: slug } }, where),
payload: this.payload,
locale,
globalSlug: slug, globalSlug: slug,
}); locale,
payload: this.payload,
where: combineQueries({ globalType: { equals: slug } }, where),
})
let doc = (await Model.findOne(query, {}, options)) as any; let doc = (await Model.findOne(query, {}, options)) as any
if (!doc) { if (!doc) {
return null; return null
} }
if (doc._id) { if (doc._id) {
doc.id = doc._id; doc.id = doc._id
delete doc._id; delete doc._id
} }
doc = JSON.parse(JSON.stringify(doc)); doc = JSON.parse(JSON.stringify(doc))
doc = sanitizeInternalFields(doc); doc = sanitizeInternalFields(doc)
return doc; return doc
}; }

View File

@@ -1,89 +1,92 @@
import { PaginateOptions } from 'mongoose'; import type { PaginateOptions } from 'mongoose'
import type { FindGlobalVersions } from 'payload/database'; import type { FindGlobalVersions } from 'payload/database'
import { flattenWhereToOperators } from 'payload/database'; import type { PayloadRequest } from 'payload/types'
import { buildVersionGlobalFields } from 'payload/versions';
import { PayloadRequest } from 'payload/types'; import { flattenWhereToOperators } from 'payload/database'
import sanitizeInternalFields from './utilities/sanitizeInternalFields'; import { buildVersionGlobalFields } from 'payload/versions'
import type { MongooseAdapter } from '.';
import { buildSortParam } from './queries/buildSortParam'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession';
import { buildSortParam } from './queries/buildSortParam'
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions( export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
this: MongooseAdapter, this: MongooseAdapter,
{ {
global, global,
where,
page,
limit, limit,
sort: sortArg,
locale, locale,
page,
pagination, pagination,
skip,
req = {} as PayloadRequest, req = {} as PayloadRequest,
skip,
sort: sortArg,
where,
}, },
) { ) {
const Model = this.versions[global]; const Model = this.versions[global]
const versionFields = buildVersionGlobalFields( const versionFields = buildVersionGlobalFields(
this.payload.globals.config.find(({ slug }) => slug === global), this.payload.globals.config.find(({ slug }) => slug === global),
); )
const options = { const options = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
skip,
limit, limit,
}; skip,
let hasNearConstraint = false;
if (where) {
const constraints = flattenWhereToOperators(where);
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'));
} }
let sort; let hasNearConstraint = false
if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}
let sort
if (!hasNearConstraint) { if (!hasNearConstraint) {
sort = buildSortParam({ sort = buildSortParam({
sort: sortArg || '-updatedAt',
fields: versionFields,
timestamps: true,
config: this.payload.config, config: this.payload.config,
fields: versionFields,
locale, locale,
}); sort: sortArg || '-updatedAt',
timestamps: true,
})
} }
const query = await Model.buildQuery({ const query = await Model.buildQuery({
payload: this.payload,
locale,
where,
globalSlug: global, globalSlug: global,
}); locale,
payload: this.payload,
where,
})
const paginationOptions: PaginateOptions = { const paginationOptions: PaginateOptions = {
page, forceCountFn: hasNearConstraint,
sort,
lean: true, lean: true,
leanWithId: true, leanWithId: true,
pagination,
offset: skip, offset: skip,
useEstimatedCount: hasNearConstraint,
forceCountFn: hasNearConstraint,
options, options,
}; page,
pagination,
if (limit > 0) { sort,
paginationOptions.limit = limit; useEstimatedCount: hasNearConstraint,
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit;
} }
const result = await Model.paginate(query, paginationOptions); if (limit > 0) {
const docs = JSON.parse(JSON.stringify(result.docs)); paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
}
const result = await Model.paginate(query, paginationOptions)
const docs = JSON.parse(JSON.stringify(result.docs))
return { return {
...result, ...result,
docs: docs.map((doc) => { docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
doc.id = doc._id; doc.id = doc._id
return sanitizeInternalFields(doc); return sanitizeInternalFields(doc)
}), }),
}; }
}; }

View File

@@ -1,38 +1,40 @@
import type { MongooseQueryOptions } from 'mongoose'; import type { MongooseQueryOptions } from 'mongoose'
import type { FindOne } from 'payload/database'; import type { FindOne } from 'payload/database'
import type { Document } from 'payload/types'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types'; import type { Document } from 'payload/types'
import sanitizeInternalFields from './utilities/sanitizeInternalFields';
import type { MongooseAdapter } from '.'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const findOne: FindOne = async function findOne( export const findOne: FindOne = async function findOne(
this: MongooseAdapter, this: MongooseAdapter,
{ collection, where, locale, req = {} as PayloadRequest }, { collection, locale, req = {} as PayloadRequest, where },
) { ) {
const Model = this.collections[collection]; const Model = this.collections[collection]
const options: MongooseQueryOptions = { const options: MongooseQueryOptions = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
lean: true, lean: true,
};
const query = await Model.buildQuery({
payload: this.payload,
locale,
where,
});
const doc = await Model.findOne(query, {}, options);
if (!doc) {
return null;
} }
let result: Document = JSON.parse(JSON.stringify(doc)); const query = await Model.buildQuery({
locale,
payload: this.payload,
where,
})
const doc = await Model.findOne(query, {}, options)
if (!doc) {
return null
}
let result: Document = JSON.parse(JSON.stringify(doc))
// custom id type reset // custom id type reset
result.id = result._id; result.id = result._id
result = sanitizeInternalFields(result); result = sanitizeInternalFields(result)
return result; return result
}; }

View File

@@ -1,86 +1,89 @@
import { PaginateOptions } from 'mongoose'; import type { PaginateOptions } from 'mongoose'
import type { FindVersions } from 'payload/database'; import type { FindVersions } from 'payload/database'
import { flattenWhereToOperators } from 'payload/database'; import type { PayloadRequest } from 'payload/types'
import { PayloadRequest } from 'payload/types';
import sanitizeInternalFields from './utilities/sanitizeInternalFields'; import { flattenWhereToOperators } from 'payload/database'
import type { MongooseAdapter } from '.';
import { buildSortParam } from './queries/buildSortParam'; import type { MongooseAdapter } from '.'
import { withSession } from './withSession';
import { buildSortParam } from './queries/buildSortParam'
import sanitizeInternalFields from './utilities/sanitizeInternalFields'
import { withSession } from './withSession'
export const findVersions: FindVersions = async function findVersions( export const findVersions: FindVersions = async function findVersions(
this: MongooseAdapter, this: MongooseAdapter,
{ {
collection, collection,
where,
page,
limit, limit,
sort: sortArg,
locale, locale,
page,
pagination, pagination,
skip,
req = {} as PayloadRequest, req = {} as PayloadRequest,
skip,
sort: sortArg,
where,
}, },
) { ) {
const Model = this.versions[collection]; const Model = this.versions[collection]
const collectionConfig = this.payload.collections[collection].config; const collectionConfig = this.payload.collections[collection].config
const options = { const options = {
...withSession(this, req.transactionID), ...withSession(this, req.transactionID),
skip,
limit, limit,
}; skip,
let hasNearConstraint = false;
if (where) {
const constraints = flattenWhereToOperators(where);
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'));
} }
let sort; let hasNearConstraint = false
if (where) {
const constraints = flattenWhereToOperators(where)
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
}
let sort
if (!hasNearConstraint) { if (!hasNearConstraint) {
sort = buildSortParam({ sort = buildSortParam({
sort: sortArg || '-updatedAt',
fields: collectionConfig.fields,
timestamps: true,
config: this.payload.config, config: this.payload.config,
fields: collectionConfig.fields,
locale, locale,
}); sort: sortArg || '-updatedAt',
timestamps: true,
})
} }
const query = await Model.buildQuery({ const query = await Model.buildQuery({
payload: this.payload,
locale, locale,
payload: this.payload,
where, where,
}); })
const paginationOptions: PaginateOptions = { const paginationOptions: PaginateOptions = {
page, forceCountFn: hasNearConstraint,
sort,
limit,
lean: true, lean: true,
leanWithId: true, leanWithId: true,
pagination, limit,
offset: skip, offset: skip,
useEstimatedCount: hasNearConstraint,
forceCountFn: hasNearConstraint,
options, options,
}; page,
pagination,
if (limit > 0) { sort,
paginationOptions.limit = limit; useEstimatedCount: hasNearConstraint,
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit;
} }
const result = await Model.paginate(query, paginationOptions); if (limit > 0) {
const docs = JSON.parse(JSON.stringify(result.docs)); paginationOptions.limit = limit
// limit must also be set here, it's ignored when pagination is false
paginationOptions.options.limit = limit
}
const result = await Model.paginate(query, paginationOptions)
const docs = JSON.parse(JSON.stringify(result.docs))
return { return {
...result, ...result,
docs: docs.map((doc) => { docs: docs.map((doc) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
doc.id = doc._id; doc.id = doc._id
return sanitizeInternalFields(doc); return sanitizeInternalFields(doc)
}), }),
}; }
}; }

View File

@@ -1,108 +1,111 @@
import type { ClientSession, Connection, ConnectOptions } from 'mongoose'; import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
import mongoose from 'mongoose'; import type { Payload } from 'payload'
import { createMigration } from 'payload/database'; import type { DatabaseAdapter } from 'payload/database'
import type { Payload } from 'payload';
import type { DatabaseAdapter } from 'payload/database'; import mongoose from 'mongoose'
import { createDatabaseAdapter } from 'payload/database'; import { createDatabaseAdapter } from 'payload/database'
import { connect } from './connect'; import { createMigration } from 'payload/database'
import { init } from './init';
import { webpack } from './webpack'; import type { CollectionModel, GlobalModel } from './types'
import { createGlobal } from './createGlobal';
import { createVersion } from './createVersion'; import { connect } from './connect'
import { beginTransaction } from './transactions/beginTransaction'; import { create } from './create'
import { rollbackTransaction } from './transactions/rollbackTransaction'; import { createGlobal } from './createGlobal'
import { commitTransaction } from './transactions/commitTransaction'; import { createVersion } from './createVersion'
import { queryDrafts } from './queryDrafts'; import { deleteMany } from './deleteMany'
import { find } from './find'; import { deleteOne } from './deleteOne'
import { findGlobalVersions } from './findGlobalVersions'; import { deleteVersions } from './deleteVersions'
import { findVersions } from './findVersions'; import { destroy } from './destroy'
import { create } from './create'; import { find } from './find'
import { deleteOne } from './deleteOne'; import { findGlobal } from './findGlobal'
import { deleteVersions } from './deleteVersions'; import { findGlobalVersions } from './findGlobalVersions'
import { findGlobal } from './findGlobal'; import { findOne } from './findOne'
import { findOne } from './findOne'; import { findVersions } from './findVersions'
import { updateGlobal } from './updateGlobal'; import { init } from './init'
import { updateOne } from './updateOne'; import { queryDrafts } from './queryDrafts'
import { updateVersion } from './updateVersion'; import { beginTransaction } from './transactions/beginTransaction'
import { deleteMany } from './deleteMany'; import { commitTransaction } from './transactions/commitTransaction'
import { destroy } from './destroy'; import { rollbackTransaction } from './transactions/rollbackTransaction'
import type { CollectionModel, GlobalModel } from './types'; import { updateGlobal } from './updateGlobal'
import { updateOne } from './updateOne'
import { updateVersion } from './updateVersion'
import { webpack } from './webpack'
export interface Args { export interface Args {
/** The URL to connect to MongoDB or false to start payload and prevent connecting */ /** Set to false to disable auto-pluralization of collection names, Defaults to true */
url: string | false; autoPluralization?: boolean
migrationDir?: string;
/** Extra configuration options */ /** Extra configuration options */
connectOptions?: ConnectOptions & { connectOptions?: ConnectOptions & {
/** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */ /** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */
useFacet?: boolean; useFacet?: boolean
}; }
/** Set to false to disable auto-pluralization of collection names, Defaults to true */ migrationDir?: string
autoPluralization?: boolean; /** The URL to connect to MongoDB or false to start payload and prevent connecting */
url: false | string
} }
export type MongooseAdapter = DatabaseAdapter & export type MongooseAdapter = DatabaseAdapter &
Args & { Args & {
mongoMemoryServer: any;
collections: { collections: {
[slug: string]: CollectionModel; [slug: string]: CollectionModel
}; }
globals: GlobalModel; connection: Connection
globals: GlobalModel
mongoMemoryServer: any
sessions: Record<number | string, ClientSession>
versions: { versions: {
[slug: string]: CollectionModel [slug: string]: CollectionModel
} }
sessions: Record<string | number, ClientSession>
connection: Connection
} }
type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter
export function mongooseAdapter({ export function mongooseAdapter({
url, autoPluralization = true,
connectOptions, connectOptions,
migrationDir, migrationDir,
autoPluralization = true, url,
}: Args): MongooseAdapterResult { }: Args): MongooseAdapterResult {
function adapter({ payload }: { payload: Payload }) { function adapter({ payload }: { payload: Payload }) {
mongoose.set('strictQuery', false); mongoose.set('strictQuery', false)
return createDatabaseAdapter<MongooseAdapter>({ return createDatabaseAdapter<MongooseAdapter>({
payload,
migrationDir,
connection: undefined,
mongoMemoryServer: undefined,
sessions: {},
url,
connectOptions: connectOptions || {},
autoPluralization, autoPluralization,
globals: undefined,
collections: {},
versions: {},
connect,
destroy,
init,
webpack,
createMigration,
beginTransaction, beginTransaction,
rollbackTransaction, collections: {},
commitTransaction, commitTransaction,
queryDrafts, connect,
findOne, connectOptions: connectOptions || {},
find, connection: undefined,
create, create,
updateOne,
deleteOne,
deleteMany,
findGlobal,
createGlobal, createGlobal,
updateGlobal, createMigration,
findVersions,
findGlobalVersions,
createVersion, createVersion,
updateVersion, deleteMany,
deleteOne,
deleteVersions, deleteVersions,
}); destroy,
find,
findGlobal,
findGlobalVersions,
findOne,
findVersions,
globals: undefined,
init,
migrationDir,
mongoMemoryServer: undefined,
payload,
queryDrafts,
rollbackTransaction,
sessions: {},
updateGlobal,
updateOne,
updateVersion,
url,
versions: {},
webpack,
})
} }
return adapter; return adapter
} }

View File

@@ -1,129 +1,119 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import mongoose, { PaginateOptions } from 'mongoose'; import type { PaginateOptions } from 'mongoose'
import paginate from 'mongoose-paginate-v2'; import type { Init } from 'payload/database'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'; import type { SanitizedCollectionConfig } from 'payload/types'
import { buildVersionCollectionFields } from 'payload/versions';
import { SanitizedCollectionConfig } from 'payload/types';
import { getVersionsModelName } from 'payload/versions';
import { buildVersionGlobalFields } from 'payload/versions';
import type { Init } from 'payload/database';
import getBuildQueryPlugin from './queries/buildQuery';
import buildCollectionSchema from './models/buildCollectionSchema';
import buildSchema from './models/buildSchema';
import type { MongooseAdapter } from '.';
import { buildGlobalModel } from './models/buildGlobalModel';
import { CollectionModel } from './types';
export const init: Init = async function init( import mongoose from 'mongoose'
this: MongooseAdapter, import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
) { import paginate from 'mongoose-paginate-v2'
this.payload.config.collections.forEach( import { buildVersionGlobalFields } from 'payload/versions'
(collection: SanitizedCollectionConfig) => { import { buildVersionCollectionFields } from 'payload/versions'
const schema = buildCollectionSchema(collection, this.payload.config); import { getVersionsModelName } from 'payload/versions'
if (collection.versions) { import type { MongooseAdapter } from '.'
const versionModelName = getVersionsModelName(collection); import type { CollectionModel } from './types'
const versionCollectionFields = buildVersionCollectionFields(collection); import buildCollectionSchema from './models/buildCollectionSchema'
import { buildGlobalModel } from './models/buildGlobalModel'
import buildSchema from './models/buildSchema'
import getBuildQueryPlugin from './queries/buildQuery'
const versionSchema = buildSchema( export const init: Init = async function init(this: MongooseAdapter) {
this.payload.config, this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
versionCollectionFields, const schema = buildCollectionSchema(collection, this.payload.config)
{
disableUnique: true,
draftsEnabled: true,
options: {
timestamps: false,
minimize: false,
},
},
);
if (collection.indexes) { if (collection.versions) {
collection.indexes.forEach((index) => { const versionModelName = getVersionsModelName(collection)
// prefix 'version.' to each field in the index
const versionIndex = {
fields: {},
options: index.options,
};
Object.entries(index.fields)
.forEach(([key, value]) => {
versionIndex.fields[`version.${key}`] = value;
});
versionSchema.index(versionIndex.fields, versionIndex.options);
});
}
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }) const versionCollectionFields = buildVersionCollectionFields(collection)
.plugin(
getBuildQueryPlugin({
collectionSlug: collection.slug,
versionsFields: versionCollectionFields,
}),
);
if (collection.versions?.drafts) { const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
versionSchema.plugin(mongooseAggregatePaginate); disableUnique: true,
} draftsEnabled: true,
options: {
minimize: false,
timestamps: false,
},
})
const model = mongoose.model( if (collection.indexes) {
versionModelName, collection.indexes.forEach((index) => {
versionSchema, // prefix 'version.' to each field in the index
versionModelName, const versionIndex = {
) as CollectionModel; fields: {},
// this.payload.versions[collection.slug] = model; options: index.options,
this.versions[collection.slug] = model; }
Object.entries(index.fields).forEach(([key, value]) => {
versionIndex.fields[`version.${key}`] = value
})
versionSchema.index(versionIndex.fields, versionIndex.options)
})
}
versionSchema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }).plugin(
getBuildQueryPlugin({
collectionSlug: collection.slug,
versionsFields: versionCollectionFields,
}),
)
if (collection.versions?.drafts) {
versionSchema.plugin(mongooseAggregatePaginate)
} }
const model = mongoose.model( const model = mongoose.model(
collection.slug, versionModelName,
schema, versionSchema,
this.autoPluralization === true ? undefined : collection.slug, versionModelName,
) as CollectionModel; ) as CollectionModel
this.collections[collection.slug] = model; // this.payload.versions[collection.slug] = model;
this.versions[collection.slug] = model
}
// TS expect error only needed until we launch 2.0.0 const model = mongoose.model(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment collection.slug,
// @ts-expect-error schema,
this.payload.collections[collection.slug] = { this.autoPluralization === true ? undefined : collection.slug,
config: collection, ) as CollectionModel
}; this.collections[collection.slug] = model
},
);
const model = buildGlobalModel(this.payload.config); // TS expect error only needed until we launch 2.0.0
this.globals = model; // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
this.payload.collections[collection.slug] = {
config: collection,
}
})
const model = buildGlobalModel(this.payload.config)
this.globals = model
this.payload.config.globals.forEach((global) => { this.payload.config.globals.forEach((global) => {
if (global.versions) { if (global.versions) {
const versionModelName = getVersionsModelName(global); const versionModelName = getVersionsModelName(global)
const versionGlobalFields = buildVersionGlobalFields(global); const versionGlobalFields = buildVersionGlobalFields(global)
const versionSchema = buildSchema( const versionSchema = buildSchema(this.payload.config, versionGlobalFields, {
this.payload.config, disableUnique: true,
versionGlobalFields, draftsEnabled: true,
{ indexSortableFields: this.payload.config.indexSortableFields,
indexSortableFields: this.payload.config.indexSortableFields, options: {
disableUnique: true, minimize: false,
draftsEnabled: true, timestamps: false,
options: {
timestamps: false,
minimize: false,
},
}, },
); })
versionSchema versionSchema
.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }) .plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true })
.plugin(getBuildQueryPlugin({ versionsFields: versionGlobalFields })); .plugin(getBuildQueryPlugin({ versionsFields: versionGlobalFields }))
const versionsModel = mongoose.model( const versionsModel = mongoose.model(
versionModelName, versionModelName,
versionSchema, versionSchema,
versionModelName, versionModelName,
) as CollectionModel; ) as CollectionModel
this.versions[global.slug] = versionsModel; this.versions[global.slug] = versionsModel
} }
}); })
}; }

View File

@@ -1 +1 @@
exports.mongooseAdapter = () => ({}); exports.mongooseAdapter = () => ({})

View File

@@ -1,38 +1,41 @@
import paginate from 'mongoose-paginate-v2'; import type { PaginateOptions, Schema } from 'mongoose'
import { PaginateOptions, Schema } from 'mongoose'; import type { SanitizedConfig } from 'payload/config'
import { SanitizedConfig } from 'payload/config'; import type { SanitizedCollectionConfig } from 'payload/types'
import { SanitizedCollectionConfig } from 'payload/types';
import getBuildQueryPlugin from '../queries/buildQuery';
import buildSchema from './buildSchema';
const buildCollectionSchema = (collection: SanitizedCollectionConfig, config: SanitizedConfig, schemaOptions = {}): Schema => { import paginate from 'mongoose-paginate-v2'
const schema = buildSchema(
config, import getBuildQueryPlugin from '../queries/buildQuery'
collection.fields, import buildSchema from './buildSchema'
{
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts), const buildCollectionSchema = (
options: { collection: SanitizedCollectionConfig,
timestamps: collection.timestamps !== false, config: SanitizedConfig,
minimize: false, schemaOptions = {},
...schemaOptions, ): Schema => {
}, const schema = buildSchema(config, collection.fields, {
indexSortableFields: config.indexSortableFields, draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
indexSortableFields: config.indexSortableFields,
options: {
minimize: false,
timestamps: collection.timestamps !== false,
...schemaOptions,
}, },
); })
if (config.indexSortableFields && collection.timestamps !== false) { if (config.indexSortableFields && collection.timestamps !== false) {
schema.index({ updatedAt: 1 }); schema.index({ updatedAt: 1 })
schema.index({ createdAt: 1 }); schema.index({ createdAt: 1 })
} }
if (collection.indexes) { if (collection.indexes) {
collection.indexes.forEach((index) => { collection.indexes.forEach((index) => {
schema.index(index.fields, index.options); schema.index(index.fields, index.options)
}); })
} }
schema.plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true }) schema
.plugin(getBuildQueryPlugin({ collectionSlug: collection.slug })); .plugin<any, PaginateOptions>(paginate, { useEstimatedCount: true })
.plugin(getBuildQueryPlugin({ collectionSlug: collection.slug }))
return schema; return schema
}; }
export default buildCollectionSchema; export default buildCollectionSchema

Some files were not shown because too many files have changed in this diff Show More