Compare commits
492 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
465acf0123 | ||
|
|
a6524720f5 | ||
|
|
59fb70077d | ||
|
|
6b3f20d01b | ||
|
|
5f6a0db869 | ||
|
|
26fcfba8fd | ||
|
|
88ad2ee1e3 | ||
|
|
b76aea9b9a | ||
|
|
d770b40cc5 | ||
|
|
3e6718034f | ||
|
|
a7beb1bb8f | ||
|
|
6a79efc5f5 | ||
|
|
9697269816 | ||
|
|
af375123a8 | ||
|
|
00b66a70c9 | ||
|
|
b0135a7ee0 | ||
|
|
fe2940a447 | ||
|
|
c745de15b9 | ||
|
|
4dcf0bcad6 | ||
|
|
93f043bc79 | ||
|
|
63bceedca4 | ||
|
|
0bbb5790d5 | ||
|
|
05c8bdc16a | ||
|
|
77c14033d6 | ||
|
|
0280cdd355 | ||
|
|
891ab34a12 | ||
|
|
26939a3331 | ||
|
|
9fa4a8fcfb | ||
|
|
96608a509b | ||
|
|
8586c85fab | ||
|
|
1a76e9580f | ||
|
|
8127fe39e2 | ||
|
|
e42c4dec98 | ||
|
|
2c82ea80de | ||
|
|
428aebd73e | ||
|
|
712bd6e8b1 | ||
|
|
1ca55b41c6 | ||
|
|
81352f3d41 | ||
|
|
6fb60cdbe3 | ||
|
|
e18784dd5b | ||
|
|
ff417368ff | ||
|
|
e28c77069e | ||
|
|
37fe2df2de | ||
|
|
8ca67d5aaa | ||
|
|
096d33718d | ||
|
|
0bd335303d | ||
|
|
e8e1ba00fe | ||
|
|
a7d47c627d | ||
|
|
5e1bed3177 | ||
|
|
364014a1e9 | ||
|
|
9cd5e5aefa | ||
|
|
429a88a5a1 | ||
|
|
cf12b5fc70 | ||
|
|
5096c37874 | ||
|
|
ac592b6d0d | ||
|
|
007f7e7d18 | ||
|
|
4ba072b0c6 | ||
|
|
8a4db150f0 | ||
|
|
b28b9020ea | ||
|
|
baa73fae62 | ||
|
|
55c27b6d2f | ||
|
|
6d148d7309 | ||
|
|
8e10ecae4b | ||
|
|
ef27b9f641 | ||
|
|
2dcce0339c | ||
|
|
b649ad7bb5 | ||
|
|
3cee0be314 | ||
|
|
8fc953605a | ||
|
|
e28dfc0c93 | ||
|
|
33561a8ea2 | ||
|
|
e500b46576 | ||
|
|
e79a84d200 | ||
|
|
16e94d401b | ||
|
|
9fbabc8fd6 | ||
|
|
9bc072ccaf | ||
|
|
45905c312f | ||
|
|
2c7a15ceca | ||
|
|
28e128241e | ||
|
|
21336cd61a | ||
|
|
ff1c10c382 | ||
|
|
6e8aa5e8af | ||
|
|
32e7c56a0d | ||
|
|
e5c783df5d | ||
|
|
63698e5e88 | ||
|
|
016ad3afc9 | ||
|
|
33e1e15ca9 | ||
|
|
59918aac91 | ||
|
|
a0c3cbd68d | ||
|
|
3a15e077c6 | ||
|
|
90a9e14e9d | ||
|
|
6d3b8636f4 | ||
|
|
cb8e07f852 | ||
|
|
301be0a5d4 | ||
|
|
466589b483 | ||
|
|
6754f55ce0 | ||
|
|
84d2bacb56 | ||
|
|
739abdcd81 | ||
|
|
c7cf2d3d2c | ||
|
|
79561497f9 | ||
|
|
600306274e | ||
|
|
398378a867 | ||
|
|
4e755dfde2 | ||
|
|
3634e2cc4d | ||
|
|
294fb5e574 | ||
|
|
f5f2332755 | ||
|
|
0acd7b8706 | ||
|
|
d91b44cbb3 | ||
|
|
e03a8e6b03 | ||
|
|
846485388a | ||
|
|
8d83e05948 | ||
|
|
7963d04a27 | ||
|
|
20b6b29c79 | ||
|
|
fdfdfc83f3 | ||
|
|
c154eb7e2b | ||
|
|
33686c6db8 | ||
|
|
6d6acbcfc1 | ||
|
|
4e2f2561ff | ||
|
|
63e3063b9e | ||
|
|
942cfec286 | ||
|
|
35dfaab7c2 | ||
|
|
bad363882c | ||
|
|
20a6ce8823 | ||
|
|
6797222733 | ||
|
|
edcb3933cf | ||
|
|
a0b13a5b01 | ||
|
|
5744de7ec6 | ||
|
|
8a6cbf8a4d | ||
|
|
0a4730188b | ||
|
|
ea46fadc26 | ||
|
|
40db0d0462 | ||
|
|
c0f05a1c38 | ||
|
|
52de1f6ab0 | ||
|
|
479293fe6f | ||
|
|
3c60abd61a | ||
|
|
f9807b5bd5 | ||
|
|
cb04d4a82a | ||
|
|
5f7ea17717 | ||
|
|
608ae42eff | ||
|
|
1da412a9bf | ||
|
|
324b51e778 | ||
|
|
f5cf546e19 | ||
|
|
510510cc66 | ||
|
|
69cb015e0b | ||
|
|
c6e0908076 | ||
|
|
8e188cfe61 | ||
|
|
8ae98503f5 | ||
|
|
1039f39c09 | ||
|
|
463d6bbec6 | ||
|
|
b117e73464 | ||
|
|
fd0f078586 | ||
|
|
0aa494b339 | ||
|
|
e8f05165eb | ||
|
|
a776e06111 | ||
|
|
02fc36e988 | ||
|
|
c5756ed4a1 | ||
|
|
356f174b9f | ||
|
|
1c15868b50 | ||
|
|
1180c795a3 | ||
|
|
da27a8aadb | ||
|
|
6a189c6548 | ||
|
|
8c4d2514b0 | ||
|
|
a09c4ddc3b | ||
|
|
5ca45987c4 | ||
|
|
5b07e18ff5 | ||
|
|
ea73e689ac | ||
|
|
456f29d689 | ||
|
|
50360b8769 | ||
|
|
541e41293d | ||
|
|
a78c4631b4 | ||
|
|
da8ff20616 | ||
|
|
037ccdd96e | ||
|
|
b84496e5da | ||
|
|
62ecf54f85 | ||
|
|
3fdaeb875e | ||
|
|
992be4af44 | ||
|
|
144bb81721 | ||
|
|
5ef20e3440 | ||
|
|
88b56d04f5 | ||
|
|
4b00850e41 | ||
|
|
badf2336ee | ||
|
|
6220c3d6d9 | ||
|
|
455b35dc1b | ||
|
|
b7299ab601 | ||
|
|
174f83faf7 | ||
|
|
5e41e0fb9e | ||
|
|
129a9e7675 | ||
|
|
eb57c2280e | ||
|
|
e2ad0baaaf | ||
|
|
7542a92104 | ||
|
|
0a91950f05 | ||
|
|
02809532b4 | ||
|
|
67ba131cc6 | ||
|
|
cf9795b8d8 | ||
|
|
a5f42e6a08 | ||
|
|
23e718d4d1 | ||
|
|
e8d9cab046 | ||
|
|
d43c83dad1 | ||
|
|
916f04cba0 | ||
|
|
156c3eeb08 | ||
|
|
9755a55672 | ||
|
|
774ffa14d3 | ||
|
|
e30db30b07 | ||
|
|
e71866856f | ||
|
|
242dac5b8e | ||
|
|
dda9ff01ca | ||
|
|
5b7aac08f5 | ||
|
|
2c74e9396a | ||
|
|
62b73250b4 | ||
|
|
9953e36f6b | ||
|
|
532f08c5b8 | ||
|
|
136298ac86 | ||
|
|
080e6195ef | ||
|
|
074abf4b7d | ||
|
|
08377cc5a7 | ||
|
|
641c765fb9 | ||
|
|
9f06253321 | ||
|
|
5476cdfee2 | ||
|
|
f232434b9a | ||
|
|
2a932ea28e | ||
|
|
41d3eee35f | ||
|
|
7aa57308be | ||
|
|
744a79f286 | ||
|
|
cd0bf68a61 | ||
|
|
a69c3ec476 | ||
|
|
3ac910bf6d | ||
|
|
60fca40780 | ||
|
|
733fc0b2d0 | ||
|
|
2fc03f196e | ||
|
|
7927dd485a | ||
|
|
42334263bb | ||
|
|
75fbec21a9 | ||
|
|
281239db05 | ||
|
|
59bc002aed | ||
|
|
70f3e6fc3e | ||
|
|
4925f90b5f | ||
|
|
fafec05309 | ||
|
|
76a71c1af5 | ||
|
|
2854bfebf7 | ||
|
|
d6bfba72a6 | ||
|
|
2b0f65a27f | ||
|
|
4b60845c67 | ||
|
|
562cd18622 | ||
|
|
584d865d34 | ||
|
|
5b7756e266 | ||
|
|
cfeeb9758f | ||
|
|
51058f65e5 | ||
|
|
6bc4dde23c | ||
|
|
40d6fe0b09 | ||
|
|
9881d322f3 | ||
|
|
1877d2247c | ||
|
|
1bf81f56ce | ||
|
|
606c3cf021 | ||
|
|
d112159d93 | ||
|
|
79393e8cf0 | ||
|
|
5b79067cc1 | ||
|
|
04851d0dc9 | ||
|
|
7c47e4b0d3 | ||
|
|
b734a1c422 | ||
|
|
9b22c4b654 | ||
|
|
03c2b36b84 | ||
|
|
868823d9e1 | ||
|
|
365b91e1b4 | ||
|
|
221356f6b9 | ||
|
|
1db1de1116 | ||
|
|
801f60939b | ||
|
|
dfdd334d16 | ||
|
|
a8e47088bb | ||
|
|
29d8bf0927 | ||
|
|
8a32909dcd | ||
|
|
c0eed02924 | ||
|
|
1d4df99ea7 | ||
|
|
b36deb4640 | ||
|
|
0c2e41c4be | ||
|
|
d9dd78ad00 | ||
|
|
06711be846 | ||
|
|
bf0b114b70 | ||
|
|
68b220ff73 | ||
|
|
f84b4323e2 | ||
|
|
064af8acc7 | ||
|
|
b22d157bd2 | ||
|
|
0112f4c4ab | ||
|
|
6670915323 | ||
|
|
49f117e220 | ||
|
|
8df4b15116 | ||
|
|
766b1b5286 | ||
|
|
3c9dab3b9d | ||
|
|
7d156ef555 | ||
|
|
1aa38f8fdd | ||
|
|
a2d9ef3ca6 | ||
|
|
9fbd7476fb | ||
|
|
f627277479 | ||
|
|
0d17d4f38e | ||
|
|
7e98cf94f3 | ||
|
|
6893231f85 | ||
|
|
8206c0fe8b | ||
|
|
837dcccefe | ||
|
|
3e05598b56 | ||
|
|
8128de64df | ||
|
|
b83d788d3c | ||
|
|
6e62aab66e | ||
|
|
5de3515fc8 | ||
|
|
65ac61f300 | ||
|
|
10b8d492b3 | ||
|
|
09c6cad3e8 | ||
|
|
e4df1293d2 | ||
|
|
ce84174554 | ||
|
|
ba9d6336ac | ||
|
|
e90c2c4cb7 | ||
|
|
8f086e315c | ||
|
|
542b5362d3 | ||
|
|
8626dc6b1a | ||
|
|
a110ba2dc0 | ||
|
|
85d2467d73 | ||
|
|
99f38098dd | ||
|
|
b1123a4978 | ||
|
|
4af8d56479 | ||
|
|
16118960aa | ||
|
|
2c5a737715 | ||
|
|
5fa77d40b9 | ||
|
|
e9106882f7 | ||
|
|
42f0db4251 | ||
|
|
c3533dac2a | ||
|
|
8d52e5f078 | ||
|
|
116e9ffe81 | ||
|
|
967f217346 | ||
|
|
da2a94d0b2 | ||
|
|
6c2b726fe1 | ||
|
|
4dd703a6bf | ||
|
|
762b572c51 | ||
|
|
6ca371cb8b | ||
|
|
0d8d7f358d | ||
|
|
51c2ab1672 | ||
|
|
a88f86cc3f | ||
|
|
451c8c7548 | ||
|
|
528645d407 | ||
|
|
70cf8487e7 | ||
|
|
aa09e566e0 | ||
|
|
c3d6e1b490 | ||
|
|
6580f43e53 | ||
|
|
56d7745139 | ||
|
|
ee1c7db915 | ||
|
|
b682c76dc7 | ||
|
|
0d035a9c23 | ||
|
|
8310950f7b | ||
|
|
120e2936fe | ||
|
|
884f7991c4 | ||
|
|
9664e4b96f | ||
|
|
63cd7fbd0c | ||
|
|
346a48f871 | ||
|
|
8fe3e59e76 | ||
|
|
74d6156e8d | ||
|
|
e834424a4c | ||
|
|
465d8b0245 | ||
|
|
25e9c1a50a | ||
|
|
d601cdd29e | ||
|
|
5646ce9d8f | ||
|
|
f7cacbe932 | ||
|
|
abe38520aa | ||
|
|
46a5f41721 | ||
|
|
2aea4150a0 | ||
|
|
4b2b4c3f9f | ||
|
|
b655809903 | ||
|
|
2e60830f6a | ||
|
|
752b5456b9 | ||
|
|
35f7677d48 | ||
|
|
a3b7da25bc | ||
|
|
596eea1f0a | ||
|
|
725aa3183d | ||
|
|
cf49f53809 | ||
|
|
20c7e37345 | ||
|
|
38e962f2cb | ||
|
|
3efb651589 | ||
|
|
589eb3fa15 | ||
|
|
87554e9b16 | ||
|
|
2e73938534 | ||
|
|
acf2564c73 | ||
|
|
91dba7be88 | ||
|
|
b49591c236 | ||
|
|
8458a98eff | ||
|
|
a518480292 | ||
|
|
a324feae9d | ||
|
|
551f1bd09f | ||
|
|
d5dfe4224d | ||
|
|
f1fc305ac4 | ||
|
|
37ca5d827b | ||
|
|
1aa257df4b | ||
|
|
e4843061f0 | ||
|
|
3c72f3303c | ||
|
|
60f5522e67 | ||
|
|
576af01b6f | ||
|
|
8b767a166a | ||
|
|
684c1a81a4 | ||
|
|
7a72f2f88d | ||
|
|
21ddcf07f7 | ||
|
|
03b1ee0896 | ||
|
|
e30871a96f | ||
|
|
3677cf688d | ||
|
|
863be3d852 | ||
|
|
2b4cf08f3a | ||
|
|
bd017bf56b | ||
|
|
ce7acff6d6 | ||
|
|
536d7017ee | ||
|
|
7c03e5510c | ||
|
|
ceb9b87783 | ||
|
|
42afa6b48a | ||
|
|
3c7d57a73b | ||
|
|
dcfd06c425 | ||
|
|
23be263dd2 | ||
|
|
f978299868 | ||
|
|
dbb0137ea0 | ||
|
|
51108c02ea | ||
|
|
69b97bbc59 | ||
|
|
f2399bc05a | ||
|
|
93a85dd937 | ||
|
|
8ee9724277 | ||
|
|
7c446ec71a | ||
|
|
f2451d03c1 | ||
|
|
0986282f13 | ||
|
|
d3638bcb24 | ||
|
|
f386d1caad | ||
|
|
480c7b3e21 | ||
|
|
908d5747a8 | ||
|
|
9ec2a40274 | ||
|
|
a080a6294c | ||
|
|
9be854a1a4 | ||
|
|
c76dc77e64 | ||
|
|
a42f17ca41 | ||
|
|
ed136fbc51 | ||
|
|
e3ff4c46cb | ||
|
|
6125b66286 | ||
|
|
8285bac2f5 | ||
|
|
61bb0fae53 | ||
|
|
47b9af970b | ||
|
|
731c85337b | ||
|
|
4b59fda56f | ||
|
|
2361221198 | ||
|
|
d931ba9b50 | ||
|
|
51fd1db4eb | ||
|
|
dbd4dd215a | ||
|
|
c716954e89 | ||
|
|
5be247da0a | ||
|
|
b47e84369c | ||
|
|
fe7ddf3e0f | ||
|
|
2fc9288870 | ||
|
|
f9de807daa | ||
|
|
e85ce4eaf2 | ||
|
|
5fc36333b9 | ||
|
|
2809cb910c | ||
|
|
782f8ca047 | ||
|
|
8bdbd6b073 | ||
|
|
7fbd5adaa2 | ||
|
|
324ca171a3 | ||
|
|
bbf114b822 | ||
|
|
a2a8ac9549 | ||
|
|
ae384306eb | ||
|
|
9c4e003315 | ||
|
|
9bb5470342 | ||
|
|
2f209e3e9b | ||
|
|
314ddbd44c | ||
|
|
3b78ab04c7 | ||
|
|
bb21f51f74 | ||
|
|
666c2383ba | ||
|
|
2703853edb | ||
|
|
f728fca036 | ||
|
|
368103d76d | ||
|
|
3a2462baba | ||
|
|
bd2bfbbb93 | ||
|
|
1300fc864c | ||
|
|
ef2d17922b | ||
|
|
b63dd40512 | ||
|
|
fb82567f03 | ||
|
|
b2c443e866 | ||
|
|
07d0324a6d | ||
|
|
c1e92ad27d | ||
|
|
28e481c2e2 | ||
|
|
1ceea645b6 | ||
|
|
578e5e7e58 | ||
|
|
463d00732f | ||
|
|
698a8abe6e | ||
|
|
776877291f | ||
|
|
1c25d965ac | ||
|
|
bc41f81303 | ||
|
|
648c38414e | ||
|
|
02b972e1ed | ||
|
|
dd38a08746 | ||
|
|
315b0059da | ||
|
|
4d3ea70d2b | ||
|
|
2697974694 | ||
|
|
095ccf7194 | ||
|
|
56d48885e1 | ||
|
|
5f620a2325 | ||
|
|
01d1f43d45 |
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Bug Report
|
||||
description: Create a bug report for the Payload CMS
|
||||
description: Create a bug report for Payload
|
||||
labels: ["possible-bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,15 +2,18 @@
|
||||
|
||||
<!-- Please include a summary of the pull request and any related issues it fixes. Please also include relevant motivation and context. -->
|
||||
|
||||
- [ ] I have read and understand the CONTRIBUTING.md document in this repository
|
||||
- [ ] I have read and understand the [CONTRIBUTING.md](../CONTRIBUTING.md) document in this repository.
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Please delete options that are not relevant. -->
|
||||
|
||||
- [ ] Chore (non-breaking change which does not add functionality)
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Change to the [templates](../templates/) directory (does not affect core functionality)
|
||||
- [ ] Change to the [examples](../examples/) directory (does not affect core functionality)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
## Checklist:
|
||||
|
||||
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -2,7 +2,7 @@ name: build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
node-version: [16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
run: yarn dev:generate-types fields
|
||||
|
||||
- name: Generate GraphQL schema file
|
||||
run: yarn dev:generate-graphql-schema
|
||||
run: yarn dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
@@ -83,6 +83,6 @@ jobs:
|
||||
${{ runner.os }}-npm-${{ env.cache-name }}-
|
||||
${{ runner.os }}-npm-
|
||||
${{ runner.os }}-
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm install
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -164,7 +164,7 @@ GitHub.sublime-settings
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
# MongoDB Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
"verbose": true,
|
||||
"git": {
|
||||
"commitMessage": "chore(release): v${version}",
|
||||
"requireCleanWorkingDir": true
|
||||
"requireCleanWorkingDir": false
|
||||
},
|
||||
"github": {
|
||||
"release": true
|
||||
},
|
||||
"npm": {
|
||||
"skipChecks": true
|
||||
"skipChecks": true,
|
||||
"tag": "payload-1"
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["yarn", "yarn clean", "yarn test"]
|
||||
"before:init": ["yarn", "yarn clean"]
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"tag": false
|
||||
},
|
||||
"github": {
|
||||
"release": false
|
||||
"release": true
|
||||
},
|
||||
"npm": {
|
||||
"skipChecks": true,
|
||||
"tag": "canary"
|
||||
"tag": "payload-1"
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": ["yarn", "yarn clean", "yarn test"]
|
||||
|
||||
30
.vscode/launch.json
vendored
30
.vscode/launch.json
vendored
@@ -5,17 +5,25 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"command": "yarn run dev _community",
|
||||
"name": "Run Dev Community",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"runtimeArgs": [
|
||||
"-r",
|
||||
"ts-node/register"
|
||||
],
|
||||
"args": [
|
||||
"${workspaceFolder}/test/dev.ts",
|
||||
"fields"
|
||||
]
|
||||
"type": "node-terminal",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"command": "yarn run dev fields",
|
||||
"name": "Run Dev Fields",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"command": "yarn run dev versions",
|
||||
"name": "Debug Versions",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
413
CHANGELOG.md
413
CHANGELOG.md
@@ -1,5 +1,416 @@
|
||||
|
||||
|
||||
## [1.15.10](https://github.com/payloadcms/payload/compare/v1.15.9...v1.15.10) (2025-03-10)
|
||||
|
||||
## [1.15.8](https://github.com/payloadcms/payload/compare/v1.15.7...v1.15.8) (2023-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* secures the user response from the me auth route ([#3409](https://github.com/payloadcms/payload/issues/3409)) ([26939a3](https://github.com/payloadcms/payload/commit/26939a333135810f2eebd3ebaf05885d152f6f13))
|
||||
|
||||
## [1.15.7](https://github.com/payloadcms/payload/compare/v1.15.6...v1.15.7) (2023-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hotkey's pressed keys are not unset when window is not focused ([#3400](https://github.com/payloadcms/payload/issues/3400)) ([8586c85](https://github.com/payloadcms/payload/commit/8586c85fab3009a09be9bf86c22952f85aa0ad82))
|
||||
|
||||
## [1.15.6](https://github.com/payloadcms/payload/compare/v1.15.5...v1.15.6) (2023-09-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **#3289:** removes HMR plugin from prod webpack configs ([#3319](https://github.com/payloadcms/payload/issues/3319)) ([8ca67d5](https://github.com/payloadcms/payload/commit/8ca67d5aaa99f0f45eac56766fe42e07ab4a41f1)), closes [#3289](https://github.com/payloadcms/payload/issues/3289)
|
||||
* fields with relationTo[] correctly load returned data from form submission ([#3317](https://github.com/payloadcms/payload/issues/3317)) ([096d337](https://github.com/payloadcms/payload/commit/096d33718d28cb5207027f6737982b29a0ced90d))
|
||||
* greater than equal admin filter not working ([#3306](https://github.com/payloadcms/payload/issues/3306)) ([0bd3353](https://github.com/payloadcms/payload/commit/0bd335303dff71977b46b373fbee859d11c33337))
|
||||
|
||||
## [1.15.5](https://github.com/payloadcms/payload/compare/v1.15.4...v1.15.5) (2023-09-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* corrects hasMany relationships within addFieldStatePromise ([#3300](https://github.com/payloadcms/payload/issues/3300)) ([a7d47c6](https://github.com/payloadcms/payload/commit/a7d47c627d064e92ca541f70caf0ff3d903b2d1d))
|
||||
|
||||
## [1.15.4](https://github.com/payloadcms/payload/compare/v1.15.3...v1.15.4) (2023-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **#3274:** sets full user data from fetchFullUser instead of partial jwt data ([#3279](https://github.com/payloadcms/payload/issues/3279)) ([cf12b5f](https://github.com/payloadcms/payload/commit/cf12b5fc703be6341b2b23efebd8aa85e8602567)), closes [#3274](https://github.com/payloadcms/payload/issues/3274)
|
||||
* aligns depth behaviour between local api and admin panel ([#3276](https://github.com/payloadcms/payload/issues/3276)) ([5096c37](https://github.com/payloadcms/payload/commit/5096c378743f4c5eb5f4f2f7e67e5e206cc9da40))
|
||||
* appends versions key to incoming where query ([#3287](https://github.com/payloadcms/payload/issues/3287)) ([9cd5e5a](https://github.com/payloadcms/payload/commit/9cd5e5aefaf0ee2af4f577da9578bb31bf4b0acb))
|
||||
* change scoping of `force` parameter to prevent false negation; ([#3278](https://github.com/payloadcms/payload/issues/3278)) ([429a88a](https://github.com/payloadcms/payload/commit/429a88a5a18e8905c63dfe00b78b3e71d56758fd))
|
||||
|
||||
## [1.15.3](https://github.com/payloadcms/payload/compare/v1.15.2...v1.15.3) (2023-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* draft globals always displaying unpublish button ([9bc072c](https://github.com/payloadcms/payload/commit/9bc072ccaf318c61b2c4e2a553604a24ff6a188e))
|
||||
* globals not saving updatedAt and createdAt and version dates correctly ([9fbabc8](https://github.com/payloadcms/payload/commit/9fbabc8fd6a3bea5628bea8d0acc915ddb33bb5c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improves query speed for version enabled collections ([16e94d4](https://github.com/payloadcms/payload/commit/16e94d401bd7cb82de53142c5f9a325abd31a81a))
|
||||
|
||||
## [1.15.2](https://github.com/payloadcms/payload/compare/v1.15.1...v1.15.2) (2023-08-25)
|
||||
|
||||
## [1.15.1](https://github.com/payloadcms/payload/compare/v1.15.0...v1.15.1) (2023-08-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* arrays in richtext uploads ([#3222](https://github.com/payloadcms/payload/issues/3222)) ([cb8e07f](https://github.com/payloadcms/payload/commit/cb8e07f85232a26c265872faf408644424312af6))
|
||||
* correct out of order dark-mode color variables ([#3197](https://github.com/payloadcms/payload/issues/3197)) ([3a15e07](https://github.com/payloadcms/payload/commit/3a15e077c6914aba3ef26e453fee23c89f3db829))
|
||||
* mutation type with tabs missing previous tabs ([#3196](https://github.com/payloadcms/payload/issues/3196)) ([6d3b863](https://github.com/payloadcms/payload/commit/6d3b8636f4e14a4e4155279353fa06e86fe2b25c))
|
||||
|
||||
# [1.15.0](https://github.com/payloadcms/payload/compare/v1.14.0...v1.15.0) (2023-08-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* query support for geo within and intersects + dynamic GraphQL operator types ([#3183](https://github.com/payloadcms/payload/issues/3183)) ([739abdc](https://github.com/payloadcms/payload/commit/739abdcd81176b3e812470eeea97b1be0d8c4a27))
|
||||
|
||||
# [1.14.0](https://github.com/payloadcms/payload/compare/v1.13.4...v1.14.0) (2023-08-16)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
* 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
|
||||
|
||||
* 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))
|
||||
* 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))
|
||||
* 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))
|
||||
* 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))
|
||||
|
||||
## [1.13.4](https://github.com/payloadcms/payload/compare/v1.13.3...v1.13.4) (2023-08-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `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))
|
||||
* adding and replacing similarly shaped block configs ([#3140](https://github.com/payloadcms/payload/issues/3140)) ([8e188cf](https://github.com/payloadcms/payload/commit/8e188cfe61db808c94d726967affdadf2e5abb9f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
* 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))
|
||||
* 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.7](https://github.com/payloadcms/payload/compare/v1.11.6...v1.11.7) (2023-07-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#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))
|
||||
* 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
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **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))
|
||||
|
||||
## [1.11.5](https://github.com/payloadcms/payload/compare/v1.11.4...v1.11.5) (2023-07-25)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
## [1.11.4](https://github.com/payloadcms/payload/compare/v1.11.3...v1.11.4) (2023-07-25)
|
||||
|
||||
|
||||
### 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))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### 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 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))
|
||||
* 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))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
## [1.10.5](https://github.com/payloadcms/payload/compare/v1.10.4...v1.10.5) (2023-06-30)
|
||||
|
||||
|
||||
### 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)
|
||||
* 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))
|
||||
|
||||
## [1.10.4](https://github.com/payloadcms/payload/compare/v1.10.3...v1.10.4) (2023-06-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
* 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))
|
||||
|
||||
## [1.10.1](https://github.com/payloadcms/payload/compare/v1.10.0...v1.10.1) (2023-06-22)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
|
||||
# [1.10.0](https://github.com/payloadcms/payload/compare/v1.9.5...v1.10.0) (2023-06-20)
|
||||
|
||||
|
||||
### 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))
|
||||
* [#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))
|
||||
* 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 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))
|
||||
* 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))
|
||||
* 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))
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* 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))
|
||||
* 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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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))
|
||||
|
||||
# [1.9.0](https://github.com/payloadcms/payload/compare/v1.8.6...v1.9.0) (2023-06-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### 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))
|
||||
* [#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))
|
||||
* 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))
|
||||
* 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)) ([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))
|
||||
|
||||
## [1.8.5](https://github.com/payloadcms/payload/compare/v1.8.4...v1.8.5) (2023-06-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add Bulgarian translation ([#2753](https://github.com/payloadcms/payload/issues/2753)) ([51108c0](https://github.com/payloadcms/payload/commit/51108c02ea346fd41c1b94ef7c339feec8383dd1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 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))
|
||||
* 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))
|
||||
* 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 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))
|
||||
* 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))
|
||||
* 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))
|
||||
|
||||
|
||||
## [1.8.3](https://github.com/payloadcms/payload/compare/v1.8.3...v1.8.3) (2023-05-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#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))
|
||||
* 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))
|
||||
* 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)
|
||||
|
||||
|
||||
@@ -589,7 +1000,7 @@ If not already defined, add the following to your `compilerOptions`:
|
||||
|
||||
#### ✋ Versions may need to be migrated
|
||||
|
||||
This release includes a substantial simplification / optimization of how Versions work within Payload. They are now significantly more performant and easier to understand behind-the-scenes. We've removed ~600 lines of code and have ensured that Payload can be compatible with all flavors of Mongo - including versions earlier than 4.0, Azure Cosmos MongoDB, AWS' DocumentDB and more.
|
||||
This release includes a substantial simplification / optimization of how Versions work within Payload. They are now significantly more performant and easier to understand behind-the-scenes. We've removed ~600 lines of code and have ensured that Payload can be compatible with all flavors of MongoDB - including versions earlier than 4.0, Azure Cosmos MongoDB, AWS' DocumentDB and more.
|
||||
|
||||
But, some of your draft-enabled documents may need to be migrated.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing to Payload CMS
|
||||
# Contributing to Payload
|
||||
|
||||
Below you'll find a set of guidelines for how to contribute to Payload CMS.
|
||||
Below you'll find a set of guidelines for how to contribute to Payload.
|
||||
|
||||
## Opening issues
|
||||
|
||||
@@ -20,9 +20,15 @@ Payload documentation can be found directly within its codebase and you can feel
|
||||
|
||||
If you're an incredibly awesome person and want to help us make Payload even better through new features or additions, we would be thrilled to work with you.
|
||||
|
||||
## Design Contributions
|
||||
|
||||
When it comes to design-related changes or additions, it's crucial for us to ensure a cohesive user experience and alignment with our broader design vision. Before embarking on any implementation that would affect the design or UI/UX, we ask that you **first share your design proposal** with us for review and approval.
|
||||
|
||||
Our design review ensures that proposed changes fit seamlessly with other components, both existing and planned. This step is meant to prevent unintentional design inconsistencies and to save you from investing time in implementing features that might need significant design alterations later.
|
||||
|
||||
### Before Starting
|
||||
|
||||
To help us work on new features, you can create a new feature request post in [GitHub Discussion](https://github.com/payloadcms/payload/discussions) or discuss it in our [Discord](https://discord.com/invite/r6sCXqVk3v). New functionality often has large implications across the entire Payload repo, so it is best to discuss the architecture and approach before starting work on a pull request.
|
||||
To help us work on new features, you can create a new feature request post in [GitHub Discussion](https://github.com/payloadcms/payload/discussions) or discuss it in our [Discord](https://discord.com/invite/payload). New functionality often has large implications across the entire Payload repo, so it is best to discuss the architecture and approach before starting work on a pull request.
|
||||
|
||||
### Code
|
||||
|
||||
@@ -49,8 +55,37 @@ The directory split up in this way specifically to reduce friction when creating
|
||||
|
||||
The following command will start Payload with your config: `yarn dev my-test-dir`. This command will start up Payload using your config and refresh a test database on every restart.
|
||||
|
||||
By default, it will automatically log you in with the default credentials. To disable that, you can either pass in the --no-auto-login flag (example: `yarn dev my-test-dir --no-auto-login`) or set the `PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN` environment variable to `false`.
|
||||
|
||||
If you wish to use to your own Mongo database for the `test` directory instead of using the in memory database, all you need to do is add the following env vars to the `test/dev.ts` file:
|
||||
|
||||
- `process.env.NODE_ENV`
|
||||
- `process.env.PAYLOAD_TEST_MONGO_URL`
|
||||
- Simply set `process.env.NODE_ENV` to `test` and set `process.env.PAYLOAD_TEST_MONGO_URL` to your mongo url e.g. `mongodb://127.0.0.1/your-test-db`.
|
||||
|
||||
NOTE: 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 E-Mail and `test` as password.
|
||||
|
||||
### Commits
|
||||
|
||||
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. Please follow this format when creating commits. Here are some examples:
|
||||
|
||||
- `feat: adds new feature`
|
||||
- `fix: fixes bug`
|
||||
- `docs: adds documentation`
|
||||
- `chore: does chore`
|
||||
|
||||
Here's a breakdown of the format. At the top-level, we use the following types to categorize our commits:
|
||||
|
||||
- `feat`: new feature that adds functionality. These are automatically added to the changelog when creating new releases.
|
||||
- `fix`: a fix to an existing feature. These are automatically added to the changelog when creating new releases.
|
||||
- `docs`: changes to [docs](./docs) only. These do not appear in the changelog.
|
||||
- `chore`: changes to code that is neither a fix nor a feature (e.g. refactoring, adding tests, etc.). These do not appear in the changelog.
|
||||
|
||||
If you are committing to [templates](./templates) or [examples](./examples), use the `chore` type with the proper scope, like this:
|
||||
|
||||
- `chore(templates): adds feature to template`
|
||||
- `chore(examples): fixes bug in example`
|
||||
|
||||
## Pull Requests
|
||||
|
||||
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
|
||||
121
README.md
121
README.md
@@ -1,38 +1,14 @@
|
||||
<p style="border: none; margin-bottom:0; padding-bottom: 0;" align="center">
|
||||
<a href="https://payloadcms.com">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-light.svg">
|
||||
<img width="350" alt="payload cms logo" src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-dark.svg">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">The most powerful TypeScript CMS</h3>
|
||||
<p align="center">Code-first Headless CMS that bridges the gap between CMS and application framework</p>
|
||||
|
||||
<h3 align="center">
|
||||
<a target="_blank" href="https://payloadcms.com/docs/getting-started/what-is-payload" rel="dofollow"><strong>Explore the docs</strong></a>
|
||||
·
|
||||
<a target="_blank" href="https://demo.payloadcms.com/" rel="dofollow"><strong>Try Live Demo</strong></a>
|
||||
<br />
|
||||
</h3>
|
||||
|
||||
<a href="https://payloadcms.com">
|
||||
<img width="100%" src="src/admin/assets/images/github-banner-alt.jpg" alt="Payload headless CMS Admin panel built with React" />
|
||||
</a>
|
||||
<br />
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" />
|
||||
</a>
|
||||
|
||||
<br />
|
||||
<p align="left">
|
||||
<a href="https://github.com/payloadcms/payload/actions">
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/payloadcms/payload/tests.yml?style=flat-square">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/payloadcms/payload/commits">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/payloadcms/payload?style=flat-square" alt="git commit activity"/>
|
||||
</a>
|
||||
|
||||
<a href="https://discord.com/invite/r6sCXqVk3v">
|
||||
<a href="https://discord.gg/payload">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/967097582721572934?label=Discord&color=7289da&style=flat-square" />
|
||||
</a>
|
||||
|
||||
@@ -41,28 +17,56 @@
|
||||
</a>
|
||||
|
||||
<a href="https://twitter.com/payloadcms">
|
||||
<img src="https://img.shields.io/badge/follow-payloadcms-1DA1F2?logo=twitter&style=flat-square" alt="Payload CMS Twitter" />
|
||||
<img src="https://img.shields.io/badge/follow-payloadcms-1DA1F2?logo=twitter&style=flat-square" alt="Payload Twitter" />
|
||||
</a>
|
||||
</p>
|
||||
<hr/>
|
||||
<h4>
|
||||
<a target="_blank" href="https://payloadcms.com/docs/getting-started/what-is-payload" rel="dofollow"><strong>Explore the Docs</strong></a> · <a target="_blank" href="https://payloadcms.com/community-help" rel="dofollow"><strong>Community Help</strong></a> · <a target="_blank" href="https://demo.payloadcms.com/" rel="dofollow"><strong>Try Live Demo</strong></a> · <a target="_blank" href="https://github.com/payloadcms/payload/discussions/1539" rel="dofollow"><strong>Roadmap</strong></a> · <a target="_blank" href="https://www.g2.com/products/payload-cms/reviews#reviews" rel="dofollow"><strong>View G2 Reviews</strong></a>
|
||||
</h4>
|
||||
<hr/>
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
<li>Don’t hit some third-party SaaS API, hit your own API</li>
|
||||
<li>Use your own database and own your data</li>
|
||||
<li>It's just Express - do what you want outside of Payload</li>
|
||||
<li>No need to learn how Payload works - if you know JS, you know Payload</li>
|
||||
<li>No vendor lock-in</li>
|
||||
<li>Avoid microservices hell - get everything (even auth) in one place</li>
|
||||
<li>Never touch ancient WP code again</li>
|
||||
<li>Build faster, never hit a roadblock</li>
|
||||
<li>Both admin and backend are 100% extensible</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
## ☁️ Deploy instantly with Payload Cloud.
|
||||
Create a cloud account, connect your GitHub, and [deploy in minutes](https://payloadcms.com/new).
|
||||
|
||||
<a href="https://payloadcms.com">
|
||||
<img src="https://cms.payloadcms.com/media/payload-github-header.jpg" alt="Payload headless CMS Admin panel built with React" />
|
||||
</a>
|
||||
## 🚀 Get started by self-hosting completely free, forever.
|
||||
|
||||
<br />
|
||||
Before beginning to work with Payload, make sure you have all of the [required software](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
## ⭐ Why Payload?
|
||||
```text
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
Payload is a CMS that has been designed for developers from the ground up to deliver them what they need to build great digital products. If you know JavaScript, you know Payload. It's a _code-first_ CMS, which allows us to do a lot of things right:
|
||||
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
|
||||
|
||||
- Payload gives you everything you need, but then steps back and lets you build what you want in JavaScript or TypeScript - with no unnecessary complexity brought by GUIs. You'll understand how your CMS works because you will have written it exactly how you want it.
|
||||
- Bring your own Express server and do whatever you need on top of Payload. Payload doesn't impose anything on you or your app.
|
||||
- Completely control the Admin panel by using your own React components. Swap out fields or even entire views with ease.
|
||||
- Use your data however and wherever you need thanks to auto-generated, yet fully extensible REST, GraphQL, and Local Node APIs.
|
||||
## 🖱️ One-click templates
|
||||
|
||||
<a target="_blank" href="https://payloadcms.com/" rel="dofollow"><strong>Read more on our website</strong></a>
|
||||
Jumpstart your next project by starting with a pre-made template. These are production-ready, end-to-end solutions designed to get you to market as fast as possible.
|
||||
|
||||
### [🛒 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. Comes with a beautiful, fully functional front-end complete with shopping cart, checkout, orders, and much more.
|
||||
|
||||
### [🌐 Website](https://github.com/payloadcms/payload/tree/master/templates/website)
|
||||
|
||||
Build any kind of website, blog, or portfolio from small to enterprise. Comes with a beautiful, fully functional front-end complete with posts, projects, comments, and much more.
|
||||
|
||||
We're constantly adding more templates to our [Templates Directory](https://github.com/payloadcms/payload/tree/master/templates). If you maintain your own template, consider adding the `payload-template` topic to your GitHub repository for others to find.
|
||||
|
||||
- [Official Templates](https://github.com/payloadcms/payload/tree/master/templates)
|
||||
- [Community Templates](https://github.com/topics/payload-template)
|
||||
|
||||
## ✨ Features
|
||||
|
||||
@@ -85,17 +89,6 @@ Payload is a CMS that has been designed for developers from the ground up to del
|
||||
- Highly secure thanks to HTTP-only cookies, CSRF protection, and more
|
||||
|
||||
<a target="_blank" href="https://github.com/payloadcms/payload/discussions"><strong>Request Feature</strong></a>
|
||||
## 🚀 Quick Start
|
||||
|
||||
Before beginning to work with Payload, make sure you have all of the [required software](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
From there, the easiest way to get started with Payload is to use the `create-payload-app` package:
|
||||
|
||||
```text
|
||||
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).
|
||||
|
||||
## 🗒️ Documentation
|
||||
|
||||
@@ -103,15 +96,31 @@ Check out the [Payload website](https://payloadcms.com/docs/getting-started/what
|
||||
|
||||
## 🙋 Contributing
|
||||
|
||||
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./contributing.md).
|
||||
If you want to add contributions to this repository, please follow the instructions in [contributing.md](./CONTRIBUTING.md).
|
||||
|
||||
## 📚 Examples
|
||||
|
||||
The [Examples Directory](./examples) is a great resource for learning how to setup Payload in a variety of different ways, but you can also find great examples in our blog and throughout our social media.
|
||||
|
||||
- [Examples Directory](./examples)
|
||||
- [Payload Blog](https://payloadcms.com/blog)
|
||||
- [Payload YouTube](https://www.youtube.com/@payloadcms)
|
||||
|
||||
## 🔌 Plugins
|
||||
|
||||
Payload is highly extensible and allows you to install or distribute plugins that add or remove functionality. There are both officially-supported and community-supported plugins available. If you maintain your own plugin, consider adding the `payload-plugin` topic to your GitHub repository for others to find.
|
||||
|
||||
- [Official Plugins](https://github.com/orgs/payloadcms/repositories?q=topic%3Apayload-plugin)
|
||||
- [Community Plugins](https://github.com/topics/payload-plugin)
|
||||
|
||||
## 🚨 Need help?
|
||||
|
||||
There are lots of good conversations and resources in our Github Discussions board & our Discord Server. If you're struggling with something, chances are, someone's already solved what you're up against. :point_down:
|
||||
There are lots of good conversations and resources in our Github Discussions board and our Discord Server. If you're struggling with something, chances are, someone's already solved what you're up against. :point_down:
|
||||
|
||||
- [GitHub Discussions](https://github.com/payloadcms/payload/discussions)
|
||||
- [GitHub Issues](https://github.com/payloadcms/payload/issues)
|
||||
- [Discord](https://t.co/30APlsQUPB)
|
||||
- [Community Help](https://payloadcms.com/community-help)
|
||||
|
||||
## ⭐ Like what we're doing? Give us a star
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as List } from '../../dist/admin/components/views/collections/List/Default';
|
||||
export type { Props } from '../../dist/admin/components/views/collections/Edit/types';
|
||||
export type { Props } from '../../dist/admin/components/views/collections/List/types';
|
||||
|
||||
@@ -38,7 +38,7 @@ const defaultPayloadAccess = ({ req: { user } }) => {
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</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>true</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>.
|
||||
</Banner>
|
||||
|
||||
### Access Control Types
|
||||
|
||||
@@ -11,10 +11,8 @@ 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.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
Custom components will automatically be provided with all props that the
|
||||
default component would accept.
|
||||
<strong>Tip:</strong><br />
|
||||
Custom components will automatically be provided with all props that the default component would accept.
|
||||
</Banner>
|
||||
|
||||
### Base Component Overrides
|
||||
@@ -85,6 +83,10 @@ You can override components on a Collection-by-Collection basis via each Collect
|
||||
| **`edit.SaveDraftButton`** | Replace the default `Save Draft` button with a custom component. Drafts must be enabled and autosave must be disabled. |
|
||||
| **`edit.PublishButton`** | Replace the default `Publish` button with a custom component. Drafts must be enabled. |
|
||||
| **`edit.PreviewButton`** | Replace the default `Preview` button with a custom component. |
|
||||
| **`BeforeList`** | Array of components to inject _before_ the built-in List view |
|
||||
| **`BeforeListTable`** | Array of components to inject _before_ the built-in List view's table |
|
||||
| **`AfterListTable`** | Array of components to inject _after_ the built-in List view's table |
|
||||
| **`AfterList`** | Array of components to inject _after_ the built-in List view |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -136,6 +138,43 @@ export const CustomPreviewButton: CustomPreviewButtonProps = ({
|
||||
};
|
||||
```
|
||||
|
||||
##### Custom Collection List View Example
|
||||
|
||||
Collection.ts
|
||||
|
||||
```tsx
|
||||
import { MyListComponent } from './MyListComponent';
|
||||
export const MyCollection: CollectionConfig = {
|
||||
slug: 'mycollection',
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
List: MyListComponent,
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
...
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
MyListComponent.tsx
|
||||
|
||||
```tsx
|
||||
import React from "react";
|
||||
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) => (
|
||||
<div>
|
||||
<p>
|
||||
Some text before the default list view component. If you just want to do
|
||||
that, you can also use the admin.components.list.BeforeList hook
|
||||
</p>
|
||||
<List {...props} />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### Globals
|
||||
|
||||
As with Collections, You can override components on a global-by-global basis via their `admin` property.
|
||||
@@ -221,11 +260,8 @@ const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
|
||||
<Banner type="success">
|
||||
For more information regarding the hooks that are available to you while you
|
||||
build custom components, including the <strong>useField</strong> hook,{" "}
|
||||
<a href="/docs/admin/hooks" style={{ color: "black" }}>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
build custom components, including the <strong>useField</strong> hook, [click
|
||||
here](/docs/admin/hooks).
|
||||
</Banner>
|
||||
|
||||
## Custom routes
|
||||
|
||||
@@ -138,25 +138,487 @@ The `useForm` hook can be used to interact with the form itself, and sends back
|
||||
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>
|
||||
|
||||
The `useForm` hook returns an object with the following properties:
|
||||
The `useForm` hook returns an object with the following properties: |
|
||||
|
||||
| Action | Description |
|
||||
|----------------------|---------------------------------------------------------------------|
|
||||
| **`fields`** | Deprecated. This property cannot be relied on as up-to-date. |
|
||||
| **`submit`** | Method to trigger the form to submit |
|
||||
| **`dispatchFields`** | Dispatch actions to the form field state |
|
||||
| **`validateForm`** | Trigger a validation of the form state |
|
||||
| **`createFormData`** | Create a `multipart/form-data` object from the current form's state |
|
||||
| **`disabled`** | Boolean denoting whether or not the form is disabled |
|
||||
| **`getFields`** | Gets all fields from state |
|
||||
| **`getField`** | Gets a single field from state by path |
|
||||
| **`getData`** | Returns the data stored in the form |
|
||||
| **`getSiblingData`** | Returns form sibling data for the given field path |
|
||||
| **`setModified`** | Set the form's `modified` state |
|
||||
| **`setProcessing`** | Set the form's `processing` state |
|
||||
| **`setSubmitted`** | Set the form's `submitted` state |
|
||||
| **`formRef`** | The ref from the form HTML element |
|
||||
| **`reset`** | Method to reset the form to its initial state |
|
||||
<TableWithDrawers
|
||||
columns={[
|
||||
'Action',
|
||||
'Description',
|
||||
'Example',
|
||||
]}
|
||||
rows={[
|
||||
[
|
||||
{
|
||||
value: <strong><code>fields</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Deprecated. This property cannot be relied on as up-to-date.",
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>submit</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Method to trigger the form to submit",
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>dispatchFields</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Dispatch actions to the form field state",
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>validateForm</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Trigger a validation of the form state",
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>createFormData</code></strong>,
|
||||
},
|
||||
{
|
||||
value: <>Create a <code>multipart/form-data</code> object from the current form's state</>,
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>disabled</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Boolean denoting whether or not the form is disabled",
|
||||
},
|
||||
{
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>getFields</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'Gets all fields from state',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>getField</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'Gets a single field from state by path',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>getData</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'Returns the data stored in the form',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>getSiblingData</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'Returns form sibling data for the given field path',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>setModified</code></strong>,
|
||||
},
|
||||
{
|
||||
value: <>Set the form\'s <code>modified</code> state</>,
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>setProcessing</code></strong>,
|
||||
},
|
||||
{
|
||||
value: <>Set the form\'s <code>processing</code> state</>,
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>setSubmitted</code></strong>,
|
||||
},
|
||||
{
|
||||
value: <>Set the form\'s <code>submitted</code> state</>,
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>formRef</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'The ref from the form HTML element',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>reset</code></strong>,
|
||||
},
|
||||
{
|
||||
value: 'Method to reset the form to its initial state',
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>addFieldRow</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Method to add a row on an array or block field",
|
||||
},
|
||||
{
|
||||
drawerTitle: 'addFieldRow',
|
||||
drawerDescription: 'A useful method to programtically add a row to an array or block field.',
|
||||
drawerSlug: 'addFieldRow',
|
||||
drawerContent: (
|
||||
<>
|
||||
<TableWithDrawers
|
||||
columns={[
|
||||
'Prop',
|
||||
'Description',
|
||||
]}
|
||||
rows={[
|
||||
[
|
||||
{
|
||||
value: <strong><code>path</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The path to the array or block field",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>rowIndex</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The index of the row to add",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>data</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The data to add to the row",
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<pre>
|
||||
{`import { useForm } from "payload/components/forms";
|
||||
|
||||
export const CustomArrayManager = () => {
|
||||
const { addFieldRow } = useForm()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
addFieldRow({
|
||||
path: "arrayField",
|
||||
rowIndex: 0,
|
||||
data: {
|
||||
textField: "text",
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Add Row
|
||||
</button>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: "arrayField",
|
||||
type: "array",
|
||||
fields: [
|
||||
{
|
||||
name: "textField",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "ui",
|
||||
name: "customArrayManager",
|
||||
admin: {
|
||||
components: {
|
||||
Field: CustomArrayManager,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}`}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>removeFieldRow</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Method to remove a row from an array or block field",
|
||||
},
|
||||
{
|
||||
drawerTitle: 'removeFieldRow',
|
||||
drawerDescription: 'A useful method to programtically remove a row from an array or block field.',
|
||||
drawerSlug: 'removeFieldRow',
|
||||
drawerContent: (
|
||||
<>
|
||||
<TableWithDrawers
|
||||
columns={[
|
||||
'Prop',
|
||||
'Description',
|
||||
]}
|
||||
rows={[
|
||||
[
|
||||
{
|
||||
value: <strong><code>path</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The path to the array or block field",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>rowIndex</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The index of the row to remove",
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<pre>
|
||||
{`import { useForm } from "payload/components/forms";
|
||||
|
||||
export const CustomArrayManager = () => {
|
||||
const { removeFieldRow } = useForm()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
removeFieldRow({
|
||||
path: "arrayField",
|
||||
rowIndex: 0,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Remove Row
|
||||
</button>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: "arrayField",
|
||||
type: "array",
|
||||
fields: [
|
||||
{
|
||||
name: "textField",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "ui",
|
||||
name: "customArrayManager",
|
||||
admin: {
|
||||
components: {
|
||||
Field: CustomArrayManager,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}`}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>replaceFieldRow</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "Method to replace a row from an array or block field",
|
||||
},
|
||||
{
|
||||
drawerTitle: 'replaceFieldRow',
|
||||
drawerDescription: 'A useful method to programtically replace a row from an array or block field.',
|
||||
drawerSlug: 'replaceFieldRow',
|
||||
drawerContent: (
|
||||
<>
|
||||
<TableWithDrawers
|
||||
columns={[
|
||||
'Prop',
|
||||
'Description',
|
||||
]}
|
||||
rows={[
|
||||
[
|
||||
{
|
||||
value: <strong><code>path</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The path to the array or block field",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>rowIndex</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The index of the row to replace",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value: <strong><code>data</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The data to replace within the row",
|
||||
},
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<pre>
|
||||
{`import { useForm } from "payload/components/forms";
|
||||
|
||||
export const CustomArrayManager = () => {
|
||||
const { replaceFieldRow } = useForm()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
replaceFieldRow({
|
||||
path: "arrayField",
|
||||
rowIndex: 0,
|
||||
data: {
|
||||
textField: "updated text",
|
||||
// blockType: "yourBlockSlug",
|
||||
// ^ if managing a block array, you need to specify the block type
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Replace Row
|
||||
</button>
|
||||
)
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: "arrayField",
|
||||
type: "array",
|
||||
fields: [
|
||||
{
|
||||
name: "textField",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "ui",
|
||||
name: "customArrayManager",
|
||||
admin: {
|
||||
components: {
|
||||
Field: CustomArrayManager,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}`}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
],
|
||||
]}
|
||||
/>
|
||||
|
||||
### useDocumentInfo
|
||||
|
||||
@@ -241,7 +703,7 @@ import { User } from '../payload-types.ts';
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
const { user } = useConfig<User>();
|
||||
const { user } = useAuth<User>();
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
|
||||
@@ -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.
|
||||
|
||||
| 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) |
|
||||
| `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`. |
|
||||
@@ -35,8 +35,9 @@ All options for the Admin panel are defined in your base Payload config file.
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) | |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| **`logoutRoute`** | The route for the `logout` page. |
|
||||
| **`inactivityRoute`** | The route for the `logout` inactivity page. |
|
||||
|
||||
@@ -45,8 +46,8 @@ All options for the Admin panel are defined in your base Payload config file.
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
The Payload Admin panel can only be used by one Collection that supports{" "}
|
||||
<a href="/docs/authentication/overview">Authentication</a>.
|
||||
The Payload Admin panel can only be used by one Collection that supports
|
||||
[Authentication](/docs/authentication/overview).
|
||||
</Banner>
|
||||
|
||||
To specify which Collection to use to log in to the Admin panel, pass the `admin` options a `user` key equal to the slug of the Collection that you'd like to use.
|
||||
|
||||
@@ -17,7 +17,7 @@ To enable Authentication on a collection, define an `auth` property and set it t
|
||||
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More](/docs/authentication/config#api-keys) |
|
||||
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
|
||||
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
|
||||
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
|
||||
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
|
||||
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the express `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
|
||||
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
|
||||
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More](/docs/authentication/config#forgot-password) |
|
||||
@@ -29,10 +29,12 @@ To enable Authentication on a collection, define an `auth` property and set it t
|
||||
|
||||
To integrate with third-party APIs or services, you might need the ability to generate API keys that can be used to identify as a certain user within Payload.
|
||||
|
||||
In Payload, users are essentially documents within a collection. Just like you can authenticate as a user with an email and password, which is considered as our default local auth strategy, you can also authenticate as a user with an API key. API keys are generated on a user-by-user basis, similar to email and passwords, and are meant to represent a single user.
|
||||
|
||||
For example, if you have a third-party service or external app that needs to be able to perform protected actions at its discretion, you have two options:
|
||||
|
||||
1. Create a user for the third-party app, and log in each time to receive a token before you attempt to access any protected actions
|
||||
1. Enable API key support for the Collection, where you can generate a non-expiring API key per user in the collection
|
||||
1. Enable API key support for the Collection, where you can generate a non-expiring API key per user in the collection. This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration. Alternatively, you could create a "super admin" user and assign an API key to that user so that any requests made with that API key are considered as being made by that super user.
|
||||
|
||||
Technically, both of these options will work for third-party integrations but the second option with API key is simpler, because it reduces the amount of work that your integrations need to do to be authenticated properly.
|
||||
|
||||
@@ -45,7 +47,7 @@ To enable API keys on a collection, set the `useAPIKey` auth option to `true`. F
|
||||
|
||||
#### Authenticating via API Key
|
||||
|
||||
To authenticate REST or GraphQL API requests using an API key, set the `Authorization` header. The header is case-sensitive and needs the slug of the `auth.useAPIKey` enabled collection, then " API-Key ", followed by the `apiKey` that has been assigned. Payload's built-in middleware will then assign the user document to `req.user` and handle requests with the proper access control.
|
||||
To authenticate REST or GraphQL API requests using an API key, set the `Authorization` header. The header is case-sensitive and needs the slug of the `auth.useAPIKey` enabled collection, then " API-Key ", followed by the `apiKey` that has been assigned. Payload's built-in middleware will then assign the user document to `req.user` and handle requests with the proper access control. By doing this, Payload recognizes the request being made as a request by the user associated with that API key.
|
||||
|
||||
**For example, using Fetch:**
|
||||
|
||||
@@ -59,6 +61,24 @@ const response = await fetch("http://localhost:3000/api/pages", {
|
||||
});
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
export const Customers: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
useAPIKey: true,
|
||||
disableLocalStrategy: true,
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Forgot Password
|
||||
|
||||
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
|
||||
@@ -229,3 +249,39 @@ If you pass a strategy to the `strategy` property directly, the `name` property
|
||||
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.
|
||||
|
||||
|
||||
### Admin autologin
|
||||
|
||||
For testing and demo purposes you may want to skip forcing the admin user to login in order to access the panel.
|
||||
The `admin.autologin` property is used to configure the how visitors are handled when accessing the admin panel.
|
||||
The default is that all users will have to login and this should not be enabled for environments where data needs to protected.
|
||||
|
||||
#### autoLogin Options
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`email`** | The email address 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. |
|
||||
|
||||
The recommended way to use this feature is behind an environment variable to ensure it is disabled when in production.
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
user: 'users',
|
||||
// highlight-start
|
||||
autoLogin: process.env.PAYLOAD_PUBLIC_ENABLE_AUTOLOGIN === 'true' ? {
|
||||
email: 'test@example.com',
|
||||
password: 'test',
|
||||
prefillOnly: true,
|
||||
} : false,
|
||||
// highlight-end
|
||||
},
|
||||
collections: [ /** */],
|
||||
})
|
||||
```
|
||||
|
||||
@@ -6,6 +6,11 @@ desc: Payload provides highly secure user Authentication out of the box, and you
|
||||
keywords: authentication, config, configuration, overview, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<YouTube
|
||||
id="CT4KafeJjTI"
|
||||
title="Simplified Authentication for Headless CMS: Unlocking Reusability in One Line"
|
||||
/>
|
||||
|
||||
<Banner>
|
||||
Payload provides for highly secure and customizable user Authentication out of the box, which allows for users to identify themselves to Payload.
|
||||
</Banner>
|
||||
@@ -78,9 +83,11 @@ Once enabled, each document that is created within the Collection can be thought
|
||||
|
||||
Successfully logging in returns a `JWT` (JSON web token) which is how a user will identify themselves to Payload. By providing this JWT via either an HTTP-only cookie or an `Authorization` header, Payload will automatically identify the user and add its user JWT data to the Express `req`, which is available throughout Payload including within access control, hooks, and more.
|
||||
|
||||
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">
|
||||
<strong>Tip:</strong><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.
|
||||
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>
|
||||
|
||||
### HTTP-only cookies
|
||||
|
||||
@@ -29,28 +29,30 @@ import payload from "payload";
|
||||
|
||||
const app = express();
|
||||
|
||||
payload.init({
|
||||
secret: "PAYLOAD_SECRET_KEY",
|
||||
mongoURL: "mongodb://localhost/payload",
|
||||
express: app,
|
||||
});
|
||||
const start = async () => {
|
||||
await payload.init({
|
||||
secret: "PAYLOAD_SECRET_KEY",
|
||||
mongoURL: "mongodb://localhost/payload",
|
||||
express: app,
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
const router = express.Router();
|
||||
|
||||
// Note: Payload must be initialized before the `payload.authenticate` middleware can be used
|
||||
router.use(payload.authenticate); // highlight-line
|
||||
// Note: Payload must be initialized before the `payload.authenticate` middleware can be used
|
||||
router.use(payload.authenticate); // highlight-line
|
||||
|
||||
router.get("/", (req, res) => {
|
||||
if (req.user) {
|
||||
return res.send(`Authenticated successfully as ${req.user.email}.`);
|
||||
}
|
||||
router.get("/", (req, res) => {
|
||||
if (req.user) {
|
||||
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, async () => {
|
||||
payload.logger.info(`listening on ${3000}...`);
|
||||
});
|
||||
app.listen(3000);
|
||||
};
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
62
docs/cloud/configuration.mdx
Normal file
62
docs/cloud/configuration.mdx
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Project Configuration
|
||||
label: Configuration
|
||||
order: 20
|
||||
desc: Quickly configure and deploy your Payload Cloud project in a few simple steps.
|
||||
keywords: configuration, config, settings, project, cloud, payload cloud, deploy, deployment
|
||||
---
|
||||
|
||||
### Select your plan
|
||||
|
||||
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">
|
||||
Note: All Payload Cloud teams that deploy a project require a card on file.
|
||||
This helps us prevent fraud and abuse on our platform. If you select a plan
|
||||
with a free trial, you will not be charged until your trial period is over.
|
||||
We’ll remind you 7 days before your trial ends and you can cancel anytime.
|
||||
</Banner>
|
||||
|
||||
### Project Details
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Region** | Select the region closest to your audience. This will ensure the fastest communication between your data and your client. |
|
||||
| **Project Name** | A name for your project. You can change this at any time. |
|
||||
| **Project Slug** | Choose a unique slug to identify your project. This needs to be unique for your team and you can change it any time. |
|
||||
| **Team** | Select the team you want to create the project under. If this is your first project, a personal team will be created for you automatically. You can modify your team settings and invite new members at any time from the Team Settings page. |
|
||||
|
||||
### Build Settings
|
||||
|
||||
If you are deploying a new project from a template, the following settings will be automatically configured for you. If you are using your own repository, you need to make sure your build settings are accurate for your project to deploy correctly.
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Root Directory** | The folder where your `package.json` file lives. |
|
||||
| **Install Command** | The command used to install your modules, for example: `yarn install` or `npm install` |
|
||||
| **Build Command** | The command used to build your application, for example: `yarn build` or `npm run build` |
|
||||
| **Serve Command** | The command used to serve your application, for example: `yarn serve` or `npm run serve` |
|
||||
| **Branch to Deploy** | Select the branch of your repository that you want to deploy from. This is the branch that will be used to build your project when you commit new changes. |
|
||||
| **Default Domain** | Set a default domain for your project. This must be unique and you will not able to change it. You can always add a custom domain later in your project settings. |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
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">
|
||||
Note: For security reasons, any variables you wish to provide to the Admin
|
||||
panel must be prefixed with `PAYLOAD_PUBLIC_`. Learn more
|
||||
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
|
||||
</Banner>
|
||||
|
||||
### Payment
|
||||
|
||||
Payment methods can be set per project and can be updated any time. You can use team’s default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong> All Payload Cloud teams that deploy a project require a
|
||||
card on file. This helps us prevent fraud and abuse on our platform. If you
|
||||
select a plan with a free trial, you will not be charged until your trial
|
||||
period is over. We’ll remind you 7 days before your trial ends and you can
|
||||
cancel anytime.
|
||||
</Banner>
|
||||
53
docs/cloud/creating-a-project.mdx
Normal file
53
docs/cloud/creating-a-project.mdx
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Getting Started
|
||||
label: Getting Started
|
||||
order: 10
|
||||
desc: Get started with Payload Cloud, a deployment solution specifically designed for Node + MongoDB applications.
|
||||
keywords: cloud, hosted, database, storage, email, deployment, serverless, node, mongodb, s3, aws, cloudflare, atlas, resend, payload, cms
|
||||
---
|
||||
|
||||
A deployment solution specifically designed for Node.js + MongoDB applications, offering seamless deployment of your entire stack in one place. You can get started in minutes with a one-click template or bring your own codebase with you.
|
||||
|
||||
Payload Cloud offers various plans tailored to meet your specific needs, including a MongoDB Atlas database, S3 file storage, and email delivery powered by [Resend](https://resend.com). To see a full breakdown of features and plans, see our [Cloud Pricing page](https://payloadcms.com/cloud-pricing).
|
||||
|
||||
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">
|
||||
To create your first project, you can either select [a
|
||||
template](#starting-from-a-template) or [import an existing
|
||||
project](#importing-from-an-existing-codebase) from GitHub.
|
||||
</Banner>
|
||||
|
||||
## Starting from a Template
|
||||
|
||||
Templates come preconfigured and provide a one-click solution to quickly deploy a new application.
|
||||
|
||||

|
||||
_Creating a new project from a template._
|
||||
|
||||
After creating an account, select your desired template from the Projects page. At this point, you need to connect to authorize the Payload Cloud application with your GitHub account. Click Continue with GitHub and follow the prompts to authorize the app.
|
||||
|
||||
Next, select your `GitHub Scope`. If you belong to multiple organizations, they will show up here. If you do not see the organization you are looking for, you may need to adjust your GitHub app permissions.
|
||||
|
||||
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">
|
||||
<strong>Note:</strong> Public repositories can be accessed by anyone online,
|
||||
while private repositories grant access only to you and anyone you explicitly
|
||||
authorize.
|
||||
</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.
|
||||
|
||||
## Importing from an Existing Codebase
|
||||
|
||||
Payload Cloud works for any Node.js + MongoDB app. From the New Project page, select **import an existing Git codebase**. Choose the organization and select the repository you want to import. From here, you will be taken to the configuration page to set up your project for deployment.
|
||||
|
||||

|
||||
_Creating a new project from an existing repository._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong> In order to make use of the features of Payload Cloud
|
||||
in your own codebase, you will need to add the [Cloud
|
||||
Plugin](https://github.com/payloadcms/plugin-cloud) to your Payload app.
|
||||
</Banner>
|
||||
110
docs/cloud/projects.mdx
Normal file
110
docs/cloud/projects.mdx
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: Cloud Projects
|
||||
label: Projects
|
||||
order: 40
|
||||
desc: Manage your Payload Cloud projects.
|
||||
keywords: cloud, payload cloud, projects, project, overview, database, file storage, build settings, environment variables, custom domains, email, developing locally
|
||||
---
|
||||
|
||||
### Overview
|
||||
|
||||
<Banner>
|
||||
The overview tab shows your most recent deployment, along with build and
|
||||
deployment logs. From here, you can see your live URL, deployment details like
|
||||
timestamps and commit hash, as well as the status of your deployment. You can
|
||||
also trigger a redeployment manually, which will rebuild your project using
|
||||
the current configuration.
|
||||
</Banner>
|
||||
|
||||

|
||||
_A screenshot of the Overview page for a Cloud project._
|
||||
|
||||
### Database
|
||||
|
||||
Your Payload Cloud project comes with a MongoDB serverless Atlas DB instance or a Dedicated Atlas cluster, depending on your plan. To interact with your cloud database, you will be provided with a MongoDB connection string. This can be found under the **Database** tab of your project.
|
||||
|
||||
`mongodb+srv://your_connection_string`
|
||||
|
||||
### File Storage
|
||||
|
||||
Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this plugin extends Payload so that all of your media will be stored in S3 rather than locally.
|
||||
|
||||
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/plugin-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
|
||||
|
||||
### Build Settings
|
||||
|
||||
You can update settings from your Project’s Settings tab. Changes to your build settings will trigger a redeployment of your project.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
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>
|
||||
Note: For security reasons, any variables you wish to provide to the Admin
|
||||
panel must be prefixed with `PAYLOAD_PUBLIC_`. Learn more
|
||||
[here](https://payloadcms.com/docs/admin/webpack#admin-environment-vars).
|
||||
</Banner>
|
||||
### Custom Domains
|
||||
|
||||
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>
|
||||
Note: do not include the protocol (http:// or https://) or any routes (/page).
|
||||
Only include the domain name and extension, and optionally a subdomain. -
|
||||
your-domain.com - backend.your-domain.com
|
||||
</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 provider’s 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.
|
||||
|
||||
You will also need to configure your Payload project to use your specified domain. In your `payload.config.ts` file, specify your `serverURL` with your domain:
|
||||
|
||||
```ts
|
||||
export default buildConfig({
|
||||
serverURL: "https://example.com",
|
||||
// the rest of your config,
|
||||
});
|
||||
```
|
||||
|
||||
### Email
|
||||
|
||||
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](https://payloadcms.com/docs/email/overview) overview.
|
||||
|
||||
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your project’s Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.
|
||||
|
||||
### Developing Locally
|
||||
|
||||
To make changes to your project, you will need to clone the repository defined in your project settings to your local machine. In order to run your project locally, you will need configure your local environment first. Refer to your repository’s `README.md` file to see the steps needed for your specific template.
|
||||
|
||||
From there, you are ready to make updates to your project. When you are ready to make your changes live, commit your changes to the branch you specified in your Project settings, and your application will automatically trigger a redeploy and build from your latest commit.
|
||||
|
||||
### Cloud Plugin
|
||||
|
||||
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload config:
|
||||
|
||||
`yarn add @payloadcms/plugin-cloud`
|
||||
|
||||
```js
|
||||
import { payloadCloud } from "@payloadcms/plugin-cloud";
|
||||
import { buildConfig } from "payload/config";
|
||||
|
||||
export default buildConfig({
|
||||
plugins: [payloadCloud()],
|
||||
// rest of config
|
||||
});
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
**Note:** If your Payload config already has an email with transport, this
|
||||
will take precedence over Payload Cloud's email service.
|
||||
</Banner>
|
||||
|
||||
##### **Optional configuration**
|
||||
|
||||
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.
|
||||
|
||||
```js
|
||||
payloadCloud({
|
||||
storage: false, // Disable file storage
|
||||
email: false, // Disable email delivery
|
||||
});
|
||||
```
|
||||
35
docs/cloud/teams.mdx
Normal file
35
docs/cloud/teams.mdx
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Cloud Teams
|
||||
label: Teams
|
||||
order: 30
|
||||
desc: Manage your Payload Cloud team and billing settings.
|
||||
keywords: team, teams, billing, subscription, payment, plan, plans, cloud, payload cloud
|
||||
---
|
||||
|
||||
<Banner>
|
||||
Within Payload Cloud, the team management feature offers you the ability to
|
||||
manage your organization, team members, billing, and subscription settings.
|
||||
</Banner>
|
||||
|
||||

|
||||
_A screenshot of the Team Settings page._
|
||||
|
||||
### Members
|
||||
|
||||
Each team has members that can interact with your projects. You can invite multiple people to your team and each individual can belong to more than one team. You can assign them either `owner` or `user` permissions. Owners are able to make admin-only changes, such as deleting projects, and editing billing information.
|
||||
|
||||
### Adding Members
|
||||
|
||||
To add a new member to your team, visit your Team’s Settings page, and click “Invite Teammate”. You can then add their email address, and assign their role. Press “Save” to send the invitations, which will send an email to the invited team member where they can create a new account.
|
||||
|
||||
### Billing
|
||||
|
||||
Users can update billing settings and subscriptions for any teams where they are designated as an `owner`. To make updates to the team’s payment methods, visit the Billing page under the Team Settings tab. You can add new cards, delete cards, and set a payment method as a default. The default payment method will be used in the event that another payment method fails.
|
||||
|
||||
### Subscriptions
|
||||
|
||||
From the Subscriptions page, a team owner can see all current plans for their team. From here, you can see the price of each plan, if there is an active trial, and when you will be billed next.
|
||||
|
||||
### Invoices
|
||||
|
||||
The Invoices page will you show you the invoices for your account, as well as the status on their payment.
|
||||
@@ -67,7 +67,7 @@ You can customize the way that the Admin panel behaves on a collection-by-collec
|
||||
|
||||
| Option | Description |
|
||||
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `group` | Text used as a label for grouping collection 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. |
|
||||
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
|
||||
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
||||
|
||||
@@ -67,6 +67,7 @@ You can customize the way that the Admin panel behaves on a Global-by-Global bas
|
||||
|
||||
| Option | Description |
|
||||
|--------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `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. |
|
||||
| `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). |
|
||||
|
||||
@@ -11,44 +11,49 @@ Not only does Payload support managing localized content, it also has internatio
|
||||
While Payload's built-in features come translated, you may want to also translate parts of your project's configuration too. This is possible in places like collections and globals labels and groups, field labels, descriptions and input placeholder text. The admin UI will display all the correct translations you provide based on the user's language.
|
||||
|
||||
Here is an example of a simple collection supporting both English and Spanish editors:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const Articles: CollectionConfig = {
|
||||
slug: 'articles',
|
||||
slug: "articles",
|
||||
labels: {
|
||||
singular: {
|
||||
en: 'Article', es: 'Artículo',
|
||||
en: "Article",
|
||||
es: "Artículo",
|
||||
},
|
||||
plural: {
|
||||
en: 'Articles', es: 'Artículos',
|
||||
en: "Articles",
|
||||
es: "Artículos",
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
group: { en: 'Content', es: 'Contenido' },
|
||||
group: { en: "Content", es: "Contenido" },
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
name: "title",
|
||||
type: "text",
|
||||
label: {
|
||||
en: 'Title', es: 'Título',
|
||||
en: "Title",
|
||||
es: "Título",
|
||||
},
|
||||
admin: {
|
||||
placeholder: { en: 'Enter title', es: 'Introduce el título' }
|
||||
}
|
||||
placeholder: { en: "Enter title", es: "Introduce el título" },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'news',
|
||||
label: { en: 'News', es: 'Noticias' },
|
||||
}, // etc...
|
||||
name: "type",
|
||||
type: "radio",
|
||||
options: [
|
||||
{
|
||||
value: "news",
|
||||
label: { en: "News", es: "Noticias" },
|
||||
}, // etc...
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Admin UI
|
||||
@@ -57,8 +62,8 @@ 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.
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong><br/>
|
||||
If there is a language that Payload does not yet support, we accept code <a href="https://github.com/payloadcms/payload/blob/master/contributing.md">contributions</a>.
|
||||
<strong>Note:</strong><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>
|
||||
|
||||
### Node Express
|
||||
@@ -76,21 +81,22 @@ In your Payload config, you can add translations and customize the settings in `
|
||||
**Example Payload config extending i18n:**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from "payload/config";
|
||||
|
||||
export default buildConfig({
|
||||
//...
|
||||
i18n: {
|
||||
fallbackLng: 'en', // default
|
||||
fallbackLng: "en", // default
|
||||
debug: false, // default
|
||||
resources: {
|
||||
en: {
|
||||
custom: { // namespace can be anything you want
|
||||
key1: 'Translation with {{variable}}', // translation
|
||||
custom: {
|
||||
// namespace can be anything you want
|
||||
key1: "Translation with {{variable}}", // translation
|
||||
},
|
||||
// override existing translation keys
|
||||
general: {
|
||||
dashboard: 'Home',
|
||||
dashboard: "Home",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -122,27 +122,36 @@ project-name
|
||||
<br />
|
||||
If you use an environment variable to configure any properties that are
|
||||
required for the Admin panel to function (ex. serverURL or any routes), you
|
||||
need to make sure that your Admin panel code can access it.{" "}
|
||||
<a href="/docs/admin/webpack#admin-environment-vars">Click here</a> for more
|
||||
info.
|
||||
need to make sure that your Admin panel code can access it. [Click
|
||||
here](/docs/admin/webpack#admin-environment-vars) for more info.
|
||||
</Banner>
|
||||
|
||||
### Customizing & overriding the config location
|
||||
### Customizing & Automating Config Location Detection
|
||||
|
||||
By default, the Payload config must be in the root of your current working directory and named either `payload.config.js` or `payload.config.ts` if you're using TypeScript.
|
||||
Payload is designed to automatically locate your configuration file. By default, it will first look in the root of your current working directory for a file named `payload.config.js` or `payload.config.ts` if you're using TypeScript.
|
||||
|
||||
But, you can specify where your Payload config is located as well as what it's named by using the environment variable `PAYLOAD_CONFIG_PATH`. The path you provide via this environment variable can either be absolute or relative to your current working directory.
|
||||
In development mode, if the configuration file is not found at the root, Payload will attempt to read your `tsconfig.json`, and search in the directory specified in `compilerOptions.rootDir` (typically "src").
|
||||
|
||||
In production mode, Payload will first attempt to find the config file in the output directory specified in `compilerOptions.outDir` of your `tsconfig.json`, then fallback to the source directory (`compilerOptions.rootDir`), and finally will check the 'dist' directory.
|
||||
|
||||
Please ensure your `tsconfig.json` is properly configured if you want Payload to accurately auto-detect your configuration file location. If `tsconfig.json` does not exist or doesn't specify `rootDir` or `outDir`, Payload will default to the current working directory.
|
||||
|
||||
#### Overriding the Config Location
|
||||
|
||||
In addition to the above automated detection, you can specify your own location for the Payload config file. This is done by using the environment variable `PAYLOAD_CONFIG_PATH`. The path you provide via this environment variable can either be absolute or relative to your current working directory. This can be useful in situations where your Payload config is not in a standard location, or you wish to switch between multiple configurations.
|
||||
|
||||
**Example in package.json:**
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js",
|
||||
}
|
||||
"scripts": {
|
||||
"dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When `PAYLOAD_CONFIG_PATH` is set, Payload will use this path to load the configuration, bypassing all automated detection.
|
||||
|
||||
### Developing within the Config
|
||||
|
||||
Payload comes with `isomorphic-fetch` installed which means that even in Node, you can use the `fetch` API just as you would within the browser. No need to import `axios` or similar, unless you want to!
|
||||
|
||||
@@ -25,21 +25,22 @@ in the `email` property object of your payload init call. Payload will make use
|
||||
|
||||
The following options are configurable in the `email` property object as part of the options object when calling payload.init().
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------------- | -------------|
|
||||
| **`fromName`** * | The name part of the From field that will be seen on the delivered email |
|
||||
| **`fromAddress`** * | The email address part of the From field that will be used when delivering email |
|
||||
| **`transport`** | The NodeMailer transport object for when you want to do it yourself, not needed when transportOptions is set |
|
||||
| **`transportOptions`** | An object that configures the transporter that Payload will create. For all the available options see the [NodeMailer documentation](https://nodemailer.com/smtp/) or see the examples below |
|
||||
| **`logMockCredentials`** | If set to true and no transport/transportOptions, ethereal credentials will be logged to console on startup |
|
||||
| Option | Description |
|
||||
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`fromName`** \* | The name part of the From field that will be seen on the delivered email |
|
||||
| **`fromAddress`** \* | The email address part of the From field that will be used when delivering email |
|
||||
| **`transport`** | The NodeMailer transport object for when you want to do it yourself, not needed when transportOptions is set |
|
||||
| **`transportOptions`** | An object that configures the transporter that Payload will create. For all the available options see the [NodeMailer documentation](https://nodemailer.com/smtp/) or see the examples below |
|
||||
| **`logMockCredentials`** | If set to true and no transport/transportOptions, ethereal credentials will be logged to console on startup |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Use SMTP
|
||||
|
||||
Simple Mail Transfer Protocol, also known as SMTP can be passed in using the `transportOptions` object on the `email` options.
|
||||
|
||||
**Example email part using SMTP:**
|
||||
|
||||
```ts
|
||||
payload.init({
|
||||
email: {
|
||||
@@ -47,24 +48,24 @@ payload.init({
|
||||
host: process.env.SMTP_HOST,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
port: 587,
|
||||
secure: true, // use TLS
|
||||
tls: {
|
||||
// do not fail on invalid certs
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
fromName: 'hello',
|
||||
fromAddress: 'hello@example.com'
|
||||
}
|
||||
},
|
||||
fromName: "hello",
|
||||
fromAddress: "hello@example.com",
|
||||
},
|
||||
// ...
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
It is best practice to avoid saving credentials or API keys directly in your code, use <a href="/docs/configuration/overview#using-environment-variables-in-your-config">environment variables</a>.
|
||||
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>
|
||||
|
||||
### Use an email service
|
||||
@@ -72,57 +73,62 @@ 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.
|
||||
|
||||
```ts
|
||||
import payload from 'payload'
|
||||
import nodemailerSendgrid from 'nodemailer-sendgrid'
|
||||
import payload from "payload";
|
||||
import nodemailerSendgrid from "nodemailer-sendgrid";
|
||||
|
||||
const sendGridAPIKey = process.env.SENDGRID_API_KEY;
|
||||
|
||||
payload.init({
|
||||
...sendGridAPIKey ? {
|
||||
email: {
|
||||
transportOptions: nodemailerSendgrid({
|
||||
apiKey: sendGridAPIKey,
|
||||
}),
|
||||
fromName: 'Admin',
|
||||
fromAddress: 'admin@example.com',
|
||||
},
|
||||
} : {},
|
||||
...(sendGridAPIKey
|
||||
? {
|
||||
email: {
|
||||
transportOptions: nodemailerSendgrid({
|
||||
apiKey: sendGridAPIKey,
|
||||
}),
|
||||
fromName: "Admin",
|
||||
fromAddress: "admin@example.com",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
```
|
||||
|
||||
### Use a custom NodeMailer transport
|
||||
|
||||
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
|
||||
import payload from 'payload'
|
||||
import nodemailer from 'nodemailer'
|
||||
import payload from "payload";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
const payload = require('payload');
|
||||
const nodemailer = require('nodemailer');
|
||||
const payload = require("payload");
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
const transport = await nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: 587,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
payload.init({
|
||||
email: {
|
||||
fromName: 'Admin',
|
||||
fromAddress: 'admin@example.com',
|
||||
transport
|
||||
fromName: "Admin",
|
||||
fromAddress: "admin@example.com",
|
||||
transport,
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### Sending Mail
|
||||
|
||||
With a working transport you can call it anywhere you have access to payload by calling `payload.sendEmail(message)`. The `message` will contain the `to`, `subject` and `email` or `text` for the email being sent. To see all available message configuration options see [NodeMailer](https://nodemailer.com/message).
|
||||
|
||||
### Mock transport
|
||||
|
||||
By default, Payload uses a mock implementation that only sends mail to the [ethereal](https://ethereal.email) capture service that will never reach a user's inbox. While in development you may wish to make use of the captured messages which is why the payload output during server output helpfully logs this out on the server console.
|
||||
|
||||
To see ethereal credentials, add `logMockCredentials: true` to the email options. This will cause them to be logged to console on startup.
|
||||
@@ -130,8 +136,8 @@ To see ethereal credentials, add `logMockCredentials: true` to the email options
|
||||
```ts
|
||||
payload.init({
|
||||
email: {
|
||||
fromName: 'Admin',
|
||||
fromAddress: 'admin@example.com',
|
||||
fromName: "Admin",
|
||||
fromAddress: "admin@example.com",
|
||||
logMockCredentials: true, // Optional
|
||||
},
|
||||
// ...
|
||||
@@ -139,11 +145,12 @@ payload.init({
|
||||
```
|
||||
|
||||
**Console output when starting payload with a mock email instance and logMockCredentials: true**
|
||||
|
||||
```
|
||||
[06:37:21] INFO (payload): Starting Payload...
|
||||
[06:37:22] INFO (payload): Payload Demo Initialized
|
||||
[06:37:22] INFO (payload): listening on 3000...
|
||||
[06:37:22] INFO (payload): Connected to Mongo server successfully!
|
||||
[06:37:22] INFO (payload): Connected to MongoDB server successfully!
|
||||
[06:37:23] INFO (payload): E-mail configured with mock configuration
|
||||
[06:37:23] INFO (payload): Log into mock email provider at https://ethereal.email
|
||||
[06:37:23] INFO (payload): Mock email account username: hhav5jw7doo4euev@ethereal.email
|
||||
|
||||
@@ -6,93 +6,104 @@ desc: Array fields are intended for sets of repeating fields, that you define. L
|
||||
keywords: array, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner >
|
||||
The Array field type is used when you need to have a set of "repeating" fields. It stores an array of objects containing the fields that you define. Its uses can be simple or highly complex.
|
||||
<Banner>
|
||||
The Array field type is used when you need to have a set of "repeating"
|
||||
fields. It stores an array of objects containing the fields that you define.
|
||||
Its uses can be simple or highly complex.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/array.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/array-dark.png"
|
||||
alt="Array field with two Rows in Payload admin panel"
|
||||
caption="Admin panel screenshot of an Array field with two Rows"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
|
||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||
|
||||

|
||||
*Admin panel screenshot of an Array field with a Row containing two text fields, a read-only text field and a checkbox*
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`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. |
|
||||
| **`fields`** * | Array of field types to correspond to each row of the Array. |
|
||||
| **`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. |
|
||||
| **`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) |
|
||||
| **`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 an array of row 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 Array will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`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. |
|
||||
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation)
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
|
||||
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
|
||||
| **`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) |
|
||||
| **`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. |
|
||||
| **`defaultValue`** | Provide an array of row 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 Array will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Admin Config
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: 'slider', // required
|
||||
type: 'array', // required
|
||||
label: 'Image Slider',
|
||||
name: "slider", // required
|
||||
type: "array", // required
|
||||
label: "Image Slider",
|
||||
minRows: 2,
|
||||
maxRows: 10,
|
||||
interfaceName: "CardSlider", // optional
|
||||
labels: {
|
||||
singular: 'Slide',
|
||||
plural: 'Slides',
|
||||
singular: "Slide",
|
||||
plural: "Slides",
|
||||
},
|
||||
fields: [ // required
|
||||
fields: [
|
||||
// required
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
name: "title",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
name: "image",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
}
|
||||
name: "caption",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: ({ data, index }) => {
|
||||
return data?.title || `Slide ${String(index).padStart(2, '0')}`;
|
||||
return data?.title || `Slide ${String(index).padStart(2, "0")}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
@@ -6,66 +6,81 @@ desc: The Blocks field type is a great layout build and can be used to construct
|
||||
keywords: blocks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner >
|
||||
The Blocks field type is <strong>incredibly powerful</strong> and can be used as a <em>layout builder</em> as well as to define any other flexible content model you can think of. It stores an array of objects, where each object must match the shape of one of your provided block configs.
|
||||
<Banner>
|
||||
The Blocks field type is <strong>incredibly powerful</strong> and can be used
|
||||
as a <em>layout builder</em> as well as to define any other flexible content
|
||||
model you can think of. It stores an array of objects, where each object must
|
||||
match the shape of one of your provided block configs.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/blocks-dark.png"
|
||||
alt="Admin panel screenshot of add Blocks drawer view"
|
||||
caption="Admin panel screenshot of add Blocks drawer view"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
|
||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||
|
||||

|
||||
*Admin panel screenshot of a Blocks field type with Call to Action and Number block examples*
|
||||
|
||||
|
||||
### Field config
|
||||
|
||||
| Option | Description |
|
||||
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`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. |
|
||||
| **`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)
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
|
||||
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
|
||||
| **`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) |
|
||||
| **`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. |
|
||||
| **`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`. || **`required`** | Require this field to have a value. |
|
||||
| **`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. |
|
||||
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`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
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
|
||||
### Block configs
|
||||
|
||||
Blocks are defined as separate configs of their own.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br />
|
||||
Best practice is to define each block config in its own file, and then import them into your Blocks field as necessary. This way each block config can be easily shared between fields. For instance, using the "layout builder" example, you might want to feature a few of the same blocks in a Post collection as well as a Page collection. Abstracting into their own files trivializes their reusability.
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
Best practice is to define each block config in its own file, and then import
|
||||
them into your Blocks field as necessary. This way each block config can be
|
||||
easily shared between fields. For instance, using the "layout builder"
|
||||
example, you might want to feature a few of the same blocks in a Post
|
||||
collection as well as a Page collection. Abstracting into their own files
|
||||
trivializes their reusability.
|
||||
</Banner>
|
||||
|
||||
| Option | Description |
|
||||
|----------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| **`slug`** * | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
||||
| **`fields`** * | Array of fields to be stored in this block. |
|
||||
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
|
||||
| **`imageURL`** | Provide a custom image thumbnail to help editors identify this block in the Admin UI. |
|
||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined |
|
||||
| Option | Description |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
||||
| **`fields`** \* | Array of fields to be stored in this block. |
|
||||
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
|
||||
| **`imageURL`** | Provide a custom image thumbnail to help editors identify this block in the Admin UI. |
|
||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
||||
|
||||
#### Auto-generated data per block
|
||||
|
||||
@@ -82,6 +97,7 @@ The Admin panel provides each block with a `blockName` field which optionally al
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.js`
|
||||
|
||||
```ts
|
||||
import { Block, CollectionConfig } from 'payload/types';
|
||||
|
||||
@@ -89,7 +105,9 @@ const QuoteBlock: Block = {
|
||||
slug: 'Quote', // required
|
||||
imageURL: 'https://google.com/path/to/image.jpg',
|
||||
imageAltText: 'A nice thumbnail image to show what this block looks like',
|
||||
fields: [ // required
|
||||
interfaceName: 'QuoteBlock', // optional
|
||||
fields: [
|
||||
// required
|
||||
{
|
||||
name: 'quoteHeader',
|
||||
type: 'text',
|
||||
@@ -99,7 +117,7 @@ const QuoteBlock: Block = {
|
||||
name: 'quoteText',
|
||||
type: 'text',
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
@@ -110,13 +128,13 @@ export const ExampleCollection: CollectionConfig = {
|
||||
type: 'blocks', // required
|
||||
minRows: 1,
|
||||
maxRows: 20,
|
||||
blocks: [ // required
|
||||
QuoteBlock
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
blocks: [
|
||||
// required
|
||||
QuoteBlock,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
@@ -125,5 +143,4 @@ As you build your own Block configs, you might want to store them in separate fi
|
||||
|
||||
```ts
|
||||
import type { Block } from 'payload/types';
|
||||
|
||||
```
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
|
||||
The Checkbox field type saves a boolean in the database.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/checkbox.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/checkbox-dark.png'
|
||||
alt='Checkbox field with text field in Payload admin panel'
|
||||
caption='Admin panel screenshot of Checkbox field with Text field below'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -11,6 +11,13 @@ keywords: code, fields, config, configuration, documentation, Content Management
|
||||
The Code field type saves a string in the database, but provides the Admin panel with a code editor styled interface.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/code.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/code-dark.png'
|
||||
alt='Shows a Code field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Code field'
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
|
||||
### Config
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/collapsible.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/collapsible-dark.png'
|
||||
alt='Shows a Collapsible field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Collapsible field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -11,6 +11,13 @@ keywords: date, fields, config, configuration, documentation, Content Management
|
||||
with a customizable time picker interface.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/date.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/date-dark.png'
|
||||
alt='Shows a Date field in the Payload admin panel'
|
||||
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.
|
||||
|
||||
### Config
|
||||
|
||||
@@ -6,10 +6,17 @@ 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
|
||||
---
|
||||
|
||||
<Banner >
|
||||
<Banner>
|
||||
The Email field enforces that the value provided is a valid email address.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/email.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/email-dark.png'
|
||||
alt='Shows an Email field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of an Email field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -6,28 +6,37 @@ desc: The Group field allows other fields to be nested under a common property.
|
||||
keywords: group, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner >
|
||||
The Group field allows fields to be nested under a common property name. It also groups fields together visually in the Admin panel.
|
||||
<Banner>
|
||||
The Group field allows fields to be nested under a common property name. It
|
||||
also groups fields together visually in the Admin panel.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/group.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/group-dark.png'
|
||||
alt='Shows a Group field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Group field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`fields`** * | Array of field types to nest within this Group. |
|
||||
| **`label`** | Used as a heading in the Admin panel and to name the generated GraphQL type. |
|
||||
| **`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. |
|
||||
| **`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) |
|
||||
| **`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 an object of 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 Group will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`fields`** \* | Array of field types to nest within this Group. |
|
||||
| **`label`** | Used as a heading in the Admin panel and to name the generated GraphQL type. |
|
||||
| **`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. |
|
||||
| **`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) |
|
||||
| **`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 an object of 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 Group will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Admin config
|
||||
|
||||
@@ -40,32 +49,35 @@ Set this property to `true` to hide this field's gutter within the admin panel.
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: 'pageMeta', // required
|
||||
type: 'group', // required
|
||||
fields: [ // required
|
||||
name: "pageMeta", // required
|
||||
type: "group", // required
|
||||
interfaceName: "Meta", // optional
|
||||
fields: [
|
||||
// required
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: true,
|
||||
minLength: 20,
|
||||
maxLength: 100,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
name: "description",
|
||||
type: "textarea",
|
||||
required: true,
|
||||
minLength: 40,
|
||||
maxLength: 160,
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
@@ -11,6 +11,13 @@ keywords: json, fields, config, configuration, documentation, Content Management
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/json.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/json-dark.png'
|
||||
alt='Shows a JSON field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a JSON field'
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
|
||||
### Config
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
The Number field stores and validates numeric entry and supports additional numerical validation and formatting features.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/number.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/number-dark.png'
|
||||
alt='Shows a Number field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Number field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
@@ -18,6 +25,9 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
|
||||
@@ -11,6 +11,13 @@ keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configurati
|
||||
The Point field type saves a pair of coordinates in the database and assigns an index for location related queries.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/point.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/point-dark.png'
|
||||
alt='Shows a Point field in the Payload admin panel'
|
||||
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.
|
||||
|
||||
### Config
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/radio.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/radio-dark.png'
|
||||
alt='Shows a Radio field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Radio field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -11,6 +11,13 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
provides for the ability to easily relate documents together.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/relationship.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/relationship-dark.png'
|
||||
alt='Shows a relationship field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Relationship field'
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
|
||||
- To add `Product` documents to an `Order` document
|
||||
@@ -25,8 +32,8 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
|
||||
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
|
||||
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
|
||||
| **`min`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
|
||||
| **`max`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
|
||||
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
|
||||
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
|
||||
| **`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. |
|
||||
@@ -47,8 +54,8 @@ _\* An asterisk denotes that a property is required._
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
The <a href="/docs/getting-started/concepts#depth">Depth</a> parameter can be
|
||||
used to automatically populate related documents that are returned by the API.
|
||||
The [Depth](/docs/getting-started/concepts#depth) parameter can be used to
|
||||
automatically populate related documents that are returned by the API.
|
||||
</Banner>
|
||||
|
||||
### Admin config
|
||||
@@ -67,7 +74,7 @@ Set to `false` if you'd like to disable the ability to create new documents from
|
||||
|
||||
Options can be dynamically limited by supplying a [query constraint](/docs/queries/overview), which will be used both for validating input and filtering available relationships in the UI.
|
||||
|
||||
The `filterOptions` property can either be a `Where` query directly, or a function that returns one. When using a function, it will be called with an argument object with the following properties:
|
||||
The `filterOptions` property can either be a `Where` query directly, or a function (synchronous or asynchronous) that returns one. When using a function, it will be called with an argument object containing the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------ |
|
||||
@@ -146,7 +153,7 @@ The shape of the data to save for a document with the field configured this way
|
||||
|
||||
```json
|
||||
{
|
||||
// Mongo ObjectID of the related user
|
||||
// MongoDB ObjectID of the related user
|
||||
"owner": "6031ac9e1289176380734024"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,10 +6,17 @@ desc: The Rich Text field allows dynamic content to be written through the Admin
|
||||
keywords: rich text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<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.
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/richtext.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/richtext-dark.png'
|
||||
alt='Shows a Rich Text field in the Payload admin panel'
|
||||
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.
|
||||
|
||||
<Banner type="success">
|
||||
@@ -18,22 +25,22 @@ The Admin component is built on the powerful [`slatejs`](https://docs.slatejs.or
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
| **`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) |
|
||||
| **`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) |
|
||||
| **`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. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`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. |
|
||||
| **`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. |
|
||||
| **`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) |
|
||||
| **`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) |
|
||||
| **`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. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`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
|
||||
|
||||
@@ -59,9 +66,11 @@ The default `elements` available in Payload are:
|
||||
- `link`
|
||||
- `ol`
|
||||
- `ul`
|
||||
- `textAlign`
|
||||
- `indent`
|
||||
- [`relationship`](#relationship-element)
|
||||
- [`upload`](#upload-element)
|
||||
- [`textAlign`](#text-align)
|
||||
|
||||
**`leaves`**
|
||||
|
||||
@@ -86,17 +95,17 @@ This allows [fields](/docs/fields/overview) to be saved as extra fields on a lin
|
||||
`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).
|
||||
|
||||

|
||||
*RichText link with custom fields*
|
||||
_RichText link with custom fields_
|
||||
|
||||
**`upload.collections[collection-name].fields`**
|
||||
|
||||
This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.
|
||||
|
||||

|
||||
*RichText field using the upload element*
|
||||
_RichText field using the upload element_
|
||||
|
||||

|
||||
*RichText upload element modal displaying fields from the config*
|
||||
_RichText upload element modal displaying fields from the config_
|
||||
|
||||
### Relationship element
|
||||
|
||||
@@ -107,12 +116,15 @@ 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.
|
||||
|
||||
<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>
|
||||
|
||||
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.
|
||||
|
||||
### TextAlign element
|
||||
|
||||
Text Alignment is not included by default and can be added to a Rich Text Editor by adding `textAlign` to the list of elements. TextAlign will alter the existing element to include a new `textAlign` field in the resulting JSON. This field can be used in combination with other elements and leaves to position content to the left, center or right.
|
||||
|
||||
### Specifying which elements and leaves to allow
|
||||
|
||||
To specify which default elements or leaves should be allowed to be used for this field, define arrays that contain string names for each element or leaf you wish to enable. To specify a custom element or leaf, pass an object with all corresponding properties as outlined below. View the [example](#example) to reference how this all works.
|
||||
@@ -125,71 +137,75 @@ Once you're up to speed with the general concepts involved, you can pass in your
|
||||
|
||||
**Both custom elements and leaves are defined via the following config:**
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **`name`** * | The name to be used as a `type` for this element. |
|
||||
| **`Button`** * | A React component to be rendered in the Rich Text toolbar. |
|
||||
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
|
||||
| Property | Description |
|
||||
| --------------- | ---------------------------------------------------------- |
|
||||
| **`name`** \* | The default name to be used as a `type` for this element. |
|
||||
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
|
||||
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
|
||||
| **`type`** | A type that overrides the default type used by `name` |
|
||||
|
||||
Custom `Element`s also require the `Element` property set to a React component to be rendered as the `Element` within the rich text editor itself.
|
||||
|
||||
Custom `Leaf` objects follow a similar pattern but require you to define the `Leaf` property instead.
|
||||
|
||||
Specifying custom `Type`s let you extend your custom elements by adding additional fields to your JSON object.
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: 'content', // required
|
||||
type: 'richText', // required
|
||||
defaultValue: [{
|
||||
children: [{ text: 'Here is some default content for this field' }],
|
||||
}],
|
||||
name: "content", // required
|
||||
type: "richText", // required
|
||||
defaultValue: [
|
||||
{
|
||||
children: [{ text: "Here is some default content for this field" }],
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'link',
|
||||
'blockquote',
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"link",
|
||||
"blockquote",
|
||||
{
|
||||
name: 'cta',
|
||||
name: "cta",
|
||||
Button: CustomCallToActionButton,
|
||||
Element: CustomCallToActionElement,
|
||||
plugins: [
|
||||
// any plugins that are required by this element go here
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
"bold",
|
||||
"italic",
|
||||
{
|
||||
name: 'highlight',
|
||||
name: "highlight",
|
||||
Button: CustomHighlightButton,
|
||||
Leaf: CustomHighlightLeaf,
|
||||
plugins: [
|
||||
// any plugins that are required by this leaf go here
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
link: {
|
||||
// Inject your own fields into the Link element
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
name: "rel",
|
||||
label: "Rel Attribute",
|
||||
type: "select",
|
||||
hasMany: true,
|
||||
options: [
|
||||
'noopener', 'noreferrer', 'nofollow',
|
||||
],
|
||||
options: ["noopener", "noreferrer", "nofollow"],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -203,9 +219,9 @@ export const ExampleCollection: CollectionConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
@@ -216,112 +232,67 @@ For more examples regarding how to define your own elements and leaves, check ou
|
||||
As the Rich Text field saves its content in a JSON format, you'll need to render it as HTML yourself. Here is an example for how to generate JSX / HTML from Rich Text content:
|
||||
|
||||
```ts
|
||||
import React, { Fragment } from 'react';
|
||||
import escapeHTML from 'escape-html';
|
||||
import { Text } from 'slate';
|
||||
import React, { Fragment } from "react";
|
||||
import escapeHTML from "escape-html";
|
||||
import { Text } from "slate";
|
||||
|
||||
const serialize = (children) => children.map((node, i) => {
|
||||
if (Text.isText(node)) {
|
||||
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />;
|
||||
|
||||
if (node.bold) {
|
||||
text = (
|
||||
<strong key={i}>
|
||||
{text}
|
||||
</strong>
|
||||
const serialize = (children) =>
|
||||
children.map((node, i) => {
|
||||
if (Text.isText(node)) {
|
||||
let text = (
|
||||
<span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
|
||||
);
|
||||
|
||||
if (node.bold) {
|
||||
text = <strong key={i}>{text}</strong>;
|
||||
}
|
||||
|
||||
if (node.code) {
|
||||
text = <code key={i}>{text}</code>;
|
||||
}
|
||||
|
||||
if (node.italic) {
|
||||
text = <em key={i}>{text}</em>;
|
||||
}
|
||||
|
||||
// Handle other leaf types here...
|
||||
|
||||
return <Fragment key={i}>{text}</Fragment>;
|
||||
}
|
||||
|
||||
if (node.code) {
|
||||
text = (
|
||||
<code key={i}>
|
||||
{text}
|
||||
</code>
|
||||
);
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.italic) {
|
||||
text = (
|
||||
<em key={i}>
|
||||
{text}
|
||||
</em>
|
||||
);
|
||||
switch (node.type) {
|
||||
case "h1":
|
||||
return <h1 key={i}>{serialize(node.children)}</h1>;
|
||||
// Iterate through all headings here...
|
||||
case "h6":
|
||||
return <h6 key={i}>{serialize(node.children)}</h6>;
|
||||
case "blockquote":
|
||||
return <blockquote key={i}>{serialize(node.children)}</blockquote>;
|
||||
case "ul":
|
||||
return <ul key={i}>{serialize(node.children)}</ul>;
|
||||
case "ol":
|
||||
return <ol key={i}>{serialize(node.children)}</ol>;
|
||||
case "li":
|
||||
return <li key={i}>{serialize(node.children)}</li>;
|
||||
case "link":
|
||||
return (
|
||||
<a href={escapeHTML(node.url)} key={i}>
|
||||
{serialize(node.children)}
|
||||
</a>
|
||||
);
|
||||
|
||||
default:
|
||||
return <p key={i}>{serialize(node.children)}</p>;
|
||||
}
|
||||
|
||||
// Handle other leaf types here...
|
||||
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{text}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'h1':
|
||||
return (
|
||||
<h1 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h1>
|
||||
);
|
||||
// Iterate through all headings here...
|
||||
case 'h6':
|
||||
return (
|
||||
<h6 key={i}>
|
||||
{serialize(node.children)}
|
||||
</h6>
|
||||
);
|
||||
case 'blockquote':
|
||||
return (
|
||||
<blockquote key={i}>
|
||||
{serialize(node.children)}
|
||||
</blockquote>
|
||||
);
|
||||
case 'ul':
|
||||
return (
|
||||
<ul key={i}>
|
||||
{serialize(node.children)}
|
||||
</ul>
|
||||
);
|
||||
case 'ol':
|
||||
return (
|
||||
<ol key={i}>
|
||||
{serialize(node.children)}
|
||||
</ol>
|
||||
);
|
||||
case 'li':
|
||||
return (
|
||||
<li key={i}>
|
||||
{serialize(node.children)}
|
||||
</li>
|
||||
);
|
||||
case 'link':
|
||||
return (
|
||||
<a
|
||||
href={escapeHTML(node.url)}
|
||||
key={i}
|
||||
>
|
||||
{serialize(node.children)}
|
||||
</a>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<p key={i}>
|
||||
{serialize(node.children)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<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>
|
||||
|
||||
### Built-in SlateJS Plugins
|
||||
@@ -337,12 +308,12 @@ If you want to utilize this functionality within your own custom elements, you c
|
||||
`customLargeBodyElement.js`:
|
||||
|
||||
```ts
|
||||
import Button from './Button';
|
||||
import Element from './Element';
|
||||
import withLargeBody from './plugin';
|
||||
import Button from "./Button";
|
||||
import Element from "./Element";
|
||||
import withLargeBody from "./plugin";
|
||||
|
||||
export default {
|
||||
name: 'large-body',
|
||||
name: "large-body",
|
||||
Button,
|
||||
Element,
|
||||
plugins: [
|
||||
@@ -350,10 +321,11 @@ export default {
|
||||
const editor = incomingEditor;
|
||||
const { shouldBreakOutOnEnter } = editor;
|
||||
|
||||
editor.shouldBreakOutOnEnter = (element) => (element.type === 'large-body' ? true : shouldBreakOutOnEnter(element));
|
||||
editor.shouldBreakOutOnEnter = (element) =>
|
||||
element.type === "large-body" ? true : shouldBreakOutOnEnter(element);
|
||||
|
||||
return editor;
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
@@ -367,8 +339,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:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
RichTextCustomElement,
|
||||
RichTextCustomLeaf,
|
||||
} from 'payload/types';
|
||||
import type { RichTextCustomElement, RichTextCustomLeaf } from "payload/types";
|
||||
```
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/row.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/row-dark.png'
|
||||
alt='Shows a row field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Row field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -10,6 +10,12 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
The Select field provides a dropdown-style interface for choosing options from
|
||||
a predefined list as an enumeration.
|
||||
</Banner>
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/select.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/select-dark.png'
|
||||
alt='Shows a Select field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of a Select field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
@@ -92,3 +98,85 @@ export const ExampleCollection: CollectionConfig = {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
The Select field UI component can be customized by providing a custom React component to the `components` object in the Base config.
|
||||
|
||||
```ts
|
||||
export const CustomSelectField: Field = {
|
||||
name: 'customSelectField',
|
||||
type: 'select', // or 'text' if you have dynamic options
|
||||
admin: {
|
||||
components: {
|
||||
Field: CustomSelectComponent({
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can import the existing Select component directly from Payload, then extend and customize it as needed.
|
||||
|
||||
```ts
|
||||
import * as React from 'react';
|
||||
import { SelectInput, useField } from 'payload/components/forms';
|
||||
import { useAuth } from 'payload/components/utilities';
|
||||
|
||||
type customSelectProps = {
|
||||
path: string;
|
||||
options: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const CustomSelectComponent: React.FC<CustomSelectProps> = ({ path, options }) => {
|
||||
const { value, setValue } = useField<string>({ path });
|
||||
const { user } = useAuth();
|
||||
|
||||
const adjustedOptions = options.filter((option) => {
|
||||
/*
|
||||
A common use case for a custom select
|
||||
is to show different options based on
|
||||
the current user's role.
|
||||
*/
|
||||
return option;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="field-label">
|
||||
Custom Select
|
||||
</label>
|
||||
<SelectInput
|
||||
path={path}
|
||||
name={path}
|
||||
options={adjustedOptions}
|
||||
value={value}
|
||||
onChange={() => setValue(e.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
If you are looking to create a dynamic select field, the following tutorial will walk you through the process of creating a custom select field that fetches its options from an external API.
|
||||
|
||||
<VideoDrawer
|
||||
id='Efn9OxSjA6Y'
|
||||
label='How to Create a Custom Select Field'
|
||||
drawerTitle='How to Create a Custom Select Field: A Step-by-Step Guide'
|
||||
/>
|
||||
|
||||
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field-component) docs.
|
||||
|
||||
@@ -6,71 +6,83 @@ desc: The Tabs field is a great way to organize complex editing experiences into
|
||||
keywords: tabs, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner >
|
||||
The Tabs field is presentational-only and only affects the Admin panel (unless a tab is named). By using it, you can place fields within a nice layout component that separates certain sub-fields by a tabbed interface.
|
||||
<Banner>
|
||||
The Tabs field is presentational-only and only affects the Admin panel (unless
|
||||
a tab is named). By using it, you can place fields within a nice layout
|
||||
component that separates certain sub-fields by a tabbed interface.
|
||||
</Banner>
|
||||
|
||||

|
||||
*Tabs field type used to separate Hero fields from Page Layout*
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/tabs.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'
|
||||
caption='Tabs field type used to separate Hero fields from Page Layout'
|
||||
/>
|
||||
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **`tabs`** * | Array of tabs to render within this Tabs field. |
|
||||
| **`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) |
|
||||
| Option | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
|
||||
| **`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) |
|
||||
|
||||
#### Tab-specific Config
|
||||
|
||||
Each tab has its own required `label` and `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 |
|
||||
| ----------------- | ----------- |
|
||||
| **`name`** | An optional property name to be used when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** * | The label to render on the tab itself. |
|
||||
| **`fields`** * | The fields to render within this tab. |
|
||||
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
|
||||
| Option | Description |
|
||||
| ---------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`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. |
|
||||
| **`fields`** \* | The fields to render within this tab. |
|
||||
| **`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) |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs', // required
|
||||
tabs: [ // required
|
||||
type: "tabs", // required
|
||||
tabs: [
|
||||
// required
|
||||
{
|
||||
label: 'Tab One Label', // required
|
||||
description: 'This will appear within the tab above the fields.',
|
||||
fields: [ // required
|
||||
label: "Tab One Label", // required
|
||||
description: "This will appear within the tab above the fields.",
|
||||
fields: [
|
||||
// required
|
||||
{
|
||||
name: 'someTextField',
|
||||
type: 'text',
|
||||
name: "someTextField",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tabTwo',
|
||||
label: 'Tab Two Label', // required
|
||||
fields: [ // required
|
||||
name: "tabTwo",
|
||||
label: "Tab Two Label", // required
|
||||
interfaceName: "TabTwo", // optional (`name` must be present)
|
||||
fields: [
|
||||
// required
|
||||
{
|
||||
name: 'numberField', // accessible via tabTwo.numberField
|
||||
type: 'number',
|
||||
name: "numberField", // accessible via tabTwo.numberField
|
||||
type: "number",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/text.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'
|
||||
caption='Admin panel screenshot of a Text field and read-only Text field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -10,6 +10,13 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
|
||||
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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/textarea.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'
|
||||
caption='Admin panel screenshot of a Textarea field and read-only Textarea field'
|
||||
/>
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
|
||||
@@ -26,13 +26,13 @@ With this field, you can also inject custom `Cell` components that appear as add
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | A unique identifier for this field. |
|
||||
| **`label`** | Human-readable label for this UI field. |
|
||||
| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | A unique identifier for this field. |
|
||||
| **`label`** | Human-readable label for this UI field. |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit view. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -6,15 +6,22 @@ desc: Upload fields will allow a file to be uploaded, only from a collection sup
|
||||
keywords: upload, images media, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<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.
|
||||
</Banner>
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br/>
|
||||
To use this field, you need to have a Collection configured to allow Uploads. For more information, <a href="/docs/upload/overview">click here</a> to read about how to enable Uploads on a collection by collection basis.
|
||||
<strong>Important:</strong><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>
|
||||
|
||||
<LightDarkImage
|
||||
srcLight='https://payloadcms.com/images/docs/fields/upload.png'
|
||||
srcDark='https://payloadcms.com/images/docs/fields/upload-dark.png'
|
||||
alt='Shows an upload field in the Payload admin panel'
|
||||
caption='Admin panel screenshot of an Upload field'
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
|
||||
- To provide a `Page` with a featured image
|
||||
@@ -23,10 +30,10 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`*relationTo`** * | Provide a single collection `slug` to allow this field to accept a relation to. <strong>Note: the related collection must be configured to support Uploads.</strong> |
|
||||
| Option | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`*relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. <strong>Note: the related collection must be configured to support Uploads.</strong> |
|
||||
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). |
|
||||
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) |
|
||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||
@@ -41,28 +48,28 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
| **`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. |
|
||||
| **`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
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
slug: "example-collection",
|
||||
fields: [
|
||||
{
|
||||
name: 'backgroundImage', // required
|
||||
type: 'upload', // required
|
||||
relationTo: 'media', // required
|
||||
name: "backgroundImage", // required
|
||||
type: "upload", // required
|
||||
relationTo: "media", // required
|
||||
required: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Filtering upload options
|
||||
@@ -71,23 +78,23 @@ Options can be dynamically limited by supplying a [query constraint](/docs/queri
|
||||
|
||||
The `filterOptions` property can either be a `Where` query directly, or a function that returns one. When using a function, it will be called with an argument object with the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| ------------- | -------------|
|
||||
| `relationTo` | The `relationTo` to filter against (as defined on the field) |
|
||||
| `data` | An object of the full collection or global document currently being edited |
|
||||
| `siblingData` | An object of the document data limited to fields within the same parent to the field |
|
||||
| `id` | The value of the collection `id`, will be `undefined` on create request |
|
||||
| `user` | The currently authenticated user object |
|
||||
| Property | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------ |
|
||||
| `relationTo` | The `relationTo` to filter against (as defined on the field) |
|
||||
| `data` | An object of the full collection or global document currently being edited |
|
||||
| `siblingData` | An object of the document data limited to fields within the same parent to the field |
|
||||
| `id` | The value of the collection `id`, will be `undefined` on create request |
|
||||
| `user` | The currently authenticated user object |
|
||||
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
const uploadField = {
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
name: "image",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
filterOptions: {
|
||||
mimeType: { contains: 'image' },
|
||||
mimeType: { contains: "image" },
|
||||
},
|
||||
};
|
||||
```
|
||||
@@ -95,6 +102,6 @@ const uploadField = {
|
||||
You can learn more about writing queries [here](/docs/queries/overview).
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong><br/>
|
||||
<strong>Note:</strong><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>
|
||||
|
||||
@@ -117,7 +117,7 @@ It is also possible to limit the depth for specific `relation` and `upload` fiel
|
||||
}
|
||||
```
|
||||
|
||||
If you were to query the Posts endpoint at, say, `http://localhost:3000/api/posts?depth=1`, you will retrieve Posts with populations one level deep. A returned result may look like the following:
|
||||
If you were to query the Posts endpoint at, say, `http://localhost:3000/api/posts?depth=1`, you will retrieve Posts with populations one level deep. This depth parameter can be thought of as N, where N is the number of levels you want to populate. To populate one level further, you would simply specify N+1 as the depth. A returned result may look like the following:
|
||||
|
||||
```
|
||||
// ?depth=1
|
||||
|
||||
@@ -11,8 +11,8 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
Payload requires the following software:
|
||||
|
||||
- Yarn or NPM
|
||||
- NodeJS version 10+
|
||||
- A Mongo Database
|
||||
- Node.js version 14+
|
||||
- A MongoDB Database
|
||||
|
||||
<Banner type="warning">
|
||||
Before proceeding any further, please ensure that you have the above
|
||||
@@ -77,12 +77,13 @@ To initialize Payload, update your `server.ts` file to reflect the following cod
|
||||
import express from "express";
|
||||
import payload from "payload";
|
||||
|
||||
require("dotenv").config();
|
||||
const app = express();
|
||||
|
||||
const start = async () => {
|
||||
await payload.init({
|
||||
secret: "SECRET_KEY",
|
||||
mongoURL: "mongodb://localhost/payload",
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
});
|
||||
|
||||
@@ -96,6 +97,13 @@ const start = async () => {
|
||||
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:
|
||||
|
||||
```
|
||||
MONGODB_URI=mongodb://127.0.0.1/your-payload-app
|
||||
PAYLOAD_SECRET=your-payload-secret
|
||||
```
|
||||
|
||||
Here is a list of all properties available to pass through `payload.init`:
|
||||
|
||||
##### `express`
|
||||
@@ -104,21 +112,21 @@ Here is a list of all properties available to pass through `payload.init`:
|
||||
|
||||
##### `secret`
|
||||
|
||||
**Required**. This is a secure string that will be used to authenticate with Payload. It can be random but should be at least 14 characters and be very difficult to guess. Often, it's smart to store this value in an `env` 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.
|
||||
**Required**. This is a secure string that will be used to authenticate with Payload. It can be random but should be at least 14 characters and be very difficult to guess.
|
||||
|
||||
Payload uses this secret key to generate secure user tokens (JWT). Behind the scenes, we do not use your secret key to encrypt directly - instead, we first take the secret key and create an encrypted string using the SHA-256 hash function. Then, we reduce the encrypted string to its first 32 characters. This final value is what Payload uses for encryption.
|
||||
|
||||
##### `mongoURL`
|
||||
|
||||
**Required**. This is a fully qualified MongoDB connection string that points to your Mongo database. If you don't have Mongo installed locally, you can [follow these steps for Mac OSX](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/) and [these steps](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/) for Windows 10. If you want to use a local database and you know you have MongoDB installed locally, a typical connection string will look like this:
|
||||
**Required**. This is a fully qualified MongoDB connection string that points to your MongoDB database. If you don't have MongoDB installed locally, you can [follow these steps for Mac OSX](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/) and [these steps](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/) for Windows 10. If you want to use a local database and you know you have MongoDB installed locally, a typical connection string will look like this:
|
||||
|
||||
`mongodb://localhost/payload`
|
||||
`mongodb://127.0.0.1/payload`
|
||||
|
||||
In contrast to running Mongo locally, a popular option is to sign up for a free [MongoDB Atlas account](https://www.mongodb.com/cloud/atlas), which is a fully hosted and cloud-based installation of Mongo that you don't need to ever worry about.
|
||||
In contrast to running MongoDB locally, a popular option is to sign up for a free [MongoDB Atlas account](https://www.mongodb.com/cloud/atlas), which is a fully hosted and cloud-based installation of MongoDB that you don't need to ever worry about.
|
||||
|
||||
##### `mongoOptions`
|
||||
|
||||
Customize Mongo connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose.
|
||||
Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose.
|
||||
|
||||
##### `email`
|
||||
|
||||
@@ -132,6 +140,10 @@ A boolean that when set to `true` tells Payload to start in local-only mode whic
|
||||
|
||||
Specify options for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=options) for more info on what is available.
|
||||
|
||||
##### `loggerDestination`
|
||||
|
||||
Specify destination stream for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=pino-destination) for more info on what is available.
|
||||
|
||||
##### `onInit`
|
||||
|
||||
A function that is called immediately following startup that receives the Payload instance as it's only argument.
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
|
||||
<YouTube
|
||||
id="In_lFhzmbME"
|
||||
title="Payload CMS Introduction - Closing the Gap Between Headless CMS and Application Frameworks"
|
||||
title="Payload Introduction - Closing the Gap Between Headless CMS and Application Frameworks"
|
||||
/>
|
||||
|
||||
<Banner type="success">
|
||||
@@ -19,7 +19,7 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
|
||||
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:
|
||||
|
||||
- A Mongo database to store your data
|
||||
- A MongoDB database to store your data
|
||||
- A way to store, retrieve, and manipulate data of any shape via full REST and GraphQL APIs
|
||||
- Authentication—complete with commonly required functionality like registration, email verification, login, & password reset
|
||||
- Deep access control to your data, based on document or field-level functions
|
||||
|
||||
@@ -80,8 +80,9 @@ Example
|
||||
async (obj, args, context, info) => { }
|
||||
```
|
||||
|
||||
**`obj`** The previous object. Not very often used and usually discarded.
|
||||
**`obj`**
|
||||
|
||||
The previous object. Not very often used and usually discarded.
|
||||
|
||||
**`args`**
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
|
||||
|
||||
When working with GraphQL it is useful to have the schema for development of other projects that need to call on your GraphQL endpoint. In Payload the schema is controlled by your collections and globals and is made available to the developer or third parties, it is not necessary for developers using Payload to write schema types manually.
|
||||
|
||||
### GraphQL Schema generate script
|
||||
### Schema generatation script
|
||||
|
||||
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:
|
||||
|
||||
@@ -16,9 +16,9 @@ Run the following command in a Payload project to generate your project's GraphQ
|
||||
payload generate:graphQLSchema
|
||||
```
|
||||
|
||||
You can run this command whenever you need to regenerate your graphQL schema and output it to a file, and then you can use the schema for writing your own graphQL elsewhere in other projects.
|
||||
You can run this command whenever you need to regenerate your GraphQL schema and output it to a file, and then you can use the schema for writing your own GraphQL elsewhere in other projects.
|
||||
|
||||
### GraphQL schema output file
|
||||
### Custom output file path
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -29,12 +29,51 @@ You can run this command whenever you need to regenerate your graphQL schema and
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Field Schemas
|
||||
|
||||
For `array`, `block`, `group` and named `tab` fields, you can generate top level reusable interfaces. The following group field config:
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
interfaceName: 'SharedMeta', <-- here!!
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
will generate:
|
||||
|
||||
```ts
|
||||
// a top level reusable type!!
|
||||
type SharedMeta {
|
||||
title: String
|
||||
description: String
|
||||
}
|
||||
|
||||
// example usage inside collection schema
|
||||
type Collection1 {
|
||||
// ...other fields
|
||||
meta: SharedMeta
|
||||
}
|
||||
```
|
||||
|
||||
The above example outputs all your definitions to a file relative from your payload config as `./graphql/schema.graphql`. By default, the file will be output to your current working directory as `schema.graphql`.
|
||||
|
||||
#### Adding an NPM script
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br/>
|
||||
<strong>Important</strong>
|
||||
<br />
|
||||
Payload needs to be able to find your config to generate your GraphQL schema.
|
||||
</Banner>
|
||||
|
||||
@@ -45,8 +84,8 @@ To add an NPM script to generate your types and show Payload where to find your
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
|
||||
},
|
||||
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ The labels you provide for your Collections and Globals are used to name the Gra
|
||||
|
||||
At the top of your Payload config you can define all the options to manage GraphQL.
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | -------------|
|
||||
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
|
||||
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
|
||||
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
|
||||
| `disablePlaygroundInProduction` | A boolean that if false will enable the graphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
|
||||
| `disable` | A boolean that if true will disable the graphQL entirely, defaults to false. |
|
||||
| Option | Description |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
|
||||
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
|
||||
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
|
||||
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
|
||||
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
|
||||
| `schemaOutputFile` | A string for the file path used by the generate schema command. Defaults to `graphql.schema` next to `payload.config.ts` [More](/docs/graphql/graphql-schema) |
|
||||
|
||||
## Collections
|
||||
@@ -43,26 +43,26 @@ export const PublicUser: CollectionConfig = {
|
||||
|
||||
**Payload will automatically open up the following queries:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | -------------|
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------------------ | -------------|
|
||||
| **`createPublicUser`** | `create` |
|
||||
| **`updatePublicUser`** | `update` |
|
||||
| **`deletePublicUser`** | `delete` |
|
||||
| Query Name | Operation |
|
||||
| ------------------------------ | ------------------------------- |
|
||||
| **`createPublicUser`** | `create` |
|
||||
| **`updatePublicUser`** | `update` |
|
||||
| **`deletePublicUser`** | `delete` |
|
||||
| **`forgotPasswordPublicUser`** | `forgotPassword` auth operation |
|
||||
| **`resetPasswordPublicUser`** | `resetPassword` auth operation |
|
||||
| **`unlockPublicUser`** | `unlock` auth operation |
|
||||
| **`verifyPublicUser`** | `verify` auth operation |
|
||||
| **`loginPublicUser`** | `login` auth operation |
|
||||
| **`logoutPublicUser`** | `logout` auth operation |
|
||||
| **`refreshTokenPublicUser`** | `refresh` auth operation |
|
||||
| **`resetPasswordPublicUser`** | `resetPassword` auth operation |
|
||||
| **`unlockPublicUser`** | `unlock` auth operation |
|
||||
| **`verifyPublicUser`** | `verify` auth operation |
|
||||
| **`loginPublicUser`** | `login` auth operation |
|
||||
| **`logoutPublicUser`** | `logout` auth operation |
|
||||
| **`refreshTokenPublicUser`** | `refresh` auth operation |
|
||||
|
||||
## Globals
|
||||
|
||||
@@ -81,32 +81,32 @@ const Header: GlobalConfig = {
|
||||
|
||||
**Payload will open the following query:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | -------------|
|
||||
| **`Header`** | `findOne` |
|
||||
| Query Name | Operation |
|
||||
| ------------ | --------- |
|
||||
| **`Header`** | `findOne` |
|
||||
|
||||
**And the following mutation:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | -------------|
|
||||
| **`updateHeader`** | `update` |
|
||||
| Query Name | Operation |
|
||||
| ------------------ | --------- |
|
||||
| **`updateHeader`** | `update` |
|
||||
|
||||
## Preferences
|
||||
|
||||
User [preferences](/docs/admin/overview#preferences) for the admin panel are also available to GraphQL. To query preferences you must supply an authorization token in the header and only the preferences of that user will be accessible and of the `key` argument.
|
||||
User [preferences](/docs/admin/overview#preferences) for the admin panel are also available to GraphQL. To query preferences you must supply an authorization token in the header and only the preferences of that user will be accessible and of the `key` argument.
|
||||
|
||||
**Payload will open the following query:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | -------------|
|
||||
| **`Preference`** | `findOne` |
|
||||
| Query Name | Operation |
|
||||
| ---------------- | --------- |
|
||||
| **`Preference`** | `findOne` |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | -------------|
|
||||
| **`updatePreference`** | `update` |
|
||||
| **`deletePreference`** | `delete` |
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | --------- |
|
||||
| **`updatePreference`** | `update` |
|
||||
| **`deletePreference`** | `delete` |
|
||||
|
||||
## GraphQL Playground
|
||||
|
||||
@@ -115,8 +115,8 @@ 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.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
To see more regarding how the above queries and mutations are used, visit your GraphQL playground (by default at <a href="http://localhost:3000/api/graphql-playground">(http://localhost:3000/api/graphql-playground</a>) 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.
|
||||
<strong>Tip:</strong><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>
|
||||
|
||||
## Query complexity limits
|
||||
|
||||
@@ -16,6 +16,7 @@ Collections feature the ability to define the following hooks:
|
||||
- [afterRead](#afterread)
|
||||
- [beforeDelete](#beforedelete)
|
||||
- [afterDelete](#afterdelete)
|
||||
- [afterOperation](#afteroperation)
|
||||
|
||||
Additionally, `auth`-enabled collections feature the following hooks:
|
||||
|
||||
@@ -31,6 +32,7 @@ Additionally, `auth`-enabled collections feature the following hooks:
|
||||
All collection Hook properties accept arrays of synchronous or asynchronous functions. Each Hook type receives specific arguments and has the ability to modify specific outputs.
|
||||
|
||||
`collections/exampleHooks.js`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
|
||||
@@ -48,6 +50,7 @@ export const ExampleHooks: CollectionConfig = {
|
||||
afterChange: [(args) => {...}],
|
||||
afterRead: [(args) => {...}],
|
||||
afterDelete: [(args) => {...}],
|
||||
afterOperation: [(args) => {...}],
|
||||
|
||||
// Auth-enabled hooks
|
||||
beforeLogin: [(args) => {...}],
|
||||
@@ -62,19 +65,19 @@ export const ExampleHooks: CollectionConfig = {
|
||||
|
||||
### beforeOperation
|
||||
|
||||
The `beforeOperation` Hook type can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
|
||||
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
|
||||
|
||||
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
|
||||
import { CollectionBeforeOperationHook } from 'payload/types';
|
||||
import { CollectionBeforeOperationHook } from "payload/types";
|
||||
|
||||
const beforeOperationHook: CollectionBeforeOperationHook = async ({
|
||||
args, // Original arguments passed into the operation
|
||||
args, // original arguments passed into the operation
|
||||
operation, // name of the operation
|
||||
}) => {
|
||||
return args; // Return operation arguments as necessary
|
||||
}
|
||||
return args; // return modified operation arguments as necessary
|
||||
};
|
||||
```
|
||||
|
||||
### beforeValidate
|
||||
@@ -88,7 +91,7 @@ Please do note that this does not run before the client-side validation. If you
|
||||
3. `validate` runs on the server
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeOperationHook } from 'payload/types';
|
||||
import { CollectionBeforeOperationHook } from "payload/types";
|
||||
|
||||
const beforeValidateHook: CollectionBeforeValidateHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -97,7 +100,7 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
|
||||
originalDoc, // original document
|
||||
}) => {
|
||||
return data; // Return data to either create or update a document with
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### beforeChange
|
||||
@@ -105,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.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeChangeHook } from 'payload/types';
|
||||
import { CollectionBeforeChangeHook } from "payload/types";
|
||||
|
||||
const beforeChangeHook: CollectionBeforeChangeHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -114,7 +117,7 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
|
||||
originalDoc, // original document
|
||||
}) => {
|
||||
return data; // Return data to either create or update a document with
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### afterChange
|
||||
@@ -122,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.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterChangeHook } from 'payload/types';
|
||||
import { CollectionAfterChangeHook } from "payload/types";
|
||||
|
||||
const afterChangeHook: CollectionAfterChangeHook = async ({
|
||||
doc, // full document data
|
||||
@@ -130,8 +133,8 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
|
||||
previousDoc, // document data before updating the collection
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
}) => {
|
||||
return doc;
|
||||
}
|
||||
return doc; // value to be used in subsequent afterChange hooks
|
||||
};
|
||||
```
|
||||
|
||||
### beforeRead
|
||||
@@ -139,7 +142,7 @@ 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.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeReadHook } from 'payload/types';
|
||||
import { CollectionBeforeReadHook } from "payload/types";
|
||||
|
||||
const beforeReadHook: CollectionBeforeReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -147,7 +150,7 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
|
||||
query, // JSON formatted query
|
||||
}) => {
|
||||
return doc;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### afterRead
|
||||
@@ -155,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.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterReadHook } from 'payload/types';
|
||||
import { CollectionAfterReadHook } from "payload/types";
|
||||
|
||||
const afterReadHook: CollectionAfterReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -164,7 +167,7 @@ const afterReadHook: CollectionAfterReadHook = async ({
|
||||
findMany, // boolean to denote if this hook is running against finding one, or finding many
|
||||
}) => {
|
||||
return doc;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### beforeDelete
|
||||
@@ -194,19 +197,37 @@ const afterDeleteHook: CollectionAfterDeleteHook = async ({
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
### afterOperation
|
||||
|
||||
The `afterOperation` hook can be used to modify the result of operations or execute side-effects that run after an operation has completed.
|
||||
|
||||
Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterOperationHook } from "payload/types";
|
||||
|
||||
const afterOperationHook: CollectionAfterOperationHook = async ({
|
||||
args, // arguments passed into the operation
|
||||
operation, // name of the operation
|
||||
result, // the result of the operation, before modifications
|
||||
}) => {
|
||||
return result; // return modified result as necessary
|
||||
};
|
||||
```
|
||||
|
||||
### beforeLogin
|
||||
|
||||
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
|
||||
import { CollectionBeforeLoginHook } from 'payload/types';
|
||||
import { CollectionBeforeLoginHook } from "payload/types";
|
||||
|
||||
const beforeLoginHook: CollectionBeforeLoginHook = async ({
|
||||
req, // full express request
|
||||
user, // user being logged in
|
||||
}) => {
|
||||
return user;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### afterLogin
|
||||
@@ -267,7 +288,7 @@ const afterMeHook: CollectionAfterMeHook = async ({
|
||||
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterForgotPasswordHook } from 'payload/types';
|
||||
import { CollectionAfterForgotPasswordHook } from "payload/types";
|
||||
|
||||
const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
|
||||
req, // full express request
|
||||
@@ -275,7 +296,7 @@ const afterLoginHook: CollectionAfterForgotPasswordHook = async ({
|
||||
token, // user token
|
||||
}) => {
|
||||
return user;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
@@ -298,5 +319,5 @@ import type {
|
||||
CollectionAfterRefreshHook,
|
||||
CollectionAfterMeHook,
|
||||
CollectionAfterForgotPasswordHook,
|
||||
} from 'payload/types';
|
||||
} from "payload/types";
|
||||
```
|
||||
|
||||
127
docs/hooks/context.mdx
Normal file
127
docs/hooks/context.mdx
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Context
|
||||
label: Context
|
||||
order: 50
|
||||
desc: Context allows you to pass in extra data that can be shared between hooks
|
||||
keywords: hooks, context, payload context, payloadcontext, data, extra data, shared data, shared, extra
|
||||
---
|
||||
|
||||
The `context` object in hooks is used to share data across different hooks. The persists throughout the entire lifecycle of a request and is available within every hook. This allows you to add logic to your hooks based on the request state by setting properties to `req.context` and using them elsewhere.
|
||||
|
||||
## When to use Context
|
||||
|
||||
Context gives you a way forward on otherwise difficult problems such as:
|
||||
|
||||
1. **Passing data between hooks**: Needing data in multiple hooks from a 3rd party API, it could be retrieved and used in `beforeChange` and later used again in an `afterChange` hook without having to fetch it twice.
|
||||
2. **Preventing infinite loops**: Calling `payload.update()` on the same document that triggered an `afterChange` hook will create an infinite loop, control the flow by assigning a no-op condition to context
|
||||
3. **Passing data to local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
|
||||
4. **Passing data between hooks and middleware or custom endpoints**: Hooks could set context across multiple collections and then be used in a final `postMiddleware`.
|
||||
|
||||
## How to Use Context
|
||||
|
||||
Let's see examples on how context can be used in the first two scenarios mentioned above:
|
||||
|
||||
### Passing data between hooks
|
||||
|
||||
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const Customer: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
hooks: {
|
||||
beforeChange: [async ({ context, data }) => {
|
||||
// assign the customerData to context for use later
|
||||
context.customerData = await fetchCustomerData(data.customerID);
|
||||
return {
|
||||
...data,
|
||||
// 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) {
|
||||
createTodo('Call Customer', context.customerData)
|
||||
}
|
||||
}],
|
||||
},
|
||||
fields: [ /* ... */ ],
|
||||
};
|
||||
```
|
||||
|
||||
### Preventing infinite loops
|
||||
|
||||
Let's say you have an `afterChange` hook, and you want to do a calculation inside the hook (as the document ID needed for the calculation is available in the `afterChange` hook, but not in the `beforeChange` hook). Once that's done, you want to update the document with the result of the calculation.
|
||||
|
||||
Bad example:
|
||||
|
||||
```ts
|
||||
const Customer: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
hooks: {
|
||||
afterChange: [async ({ doc }) => {
|
||||
await payload.update({
|
||||
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
|
||||
collection: 'customers',
|
||||
id: doc.id,
|
||||
data: {
|
||||
...(await fetchCustomerData(data.customerID))
|
||||
},
|
||||
});
|
||||
}],
|
||||
},
|
||||
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.
|
||||
|
||||
Fixed example:
|
||||
|
||||
```ts
|
||||
const MyCollection: CollectionConfig = {
|
||||
slug: 'slug',
|
||||
hooks: {
|
||||
afterChange: [async ({ context, doc }) => {
|
||||
// return if flag was previously set
|
||||
if (context.triggerAfterChange === false) {
|
||||
return;
|
||||
}
|
||||
await payload.update({
|
||||
collection: contextHooksSlug,
|
||||
id: doc.id,
|
||||
data: {
|
||||
...(await fetchCustomerData(data.customerID))
|
||||
},
|
||||
context: {
|
||||
// set a flag to prevent from running again
|
||||
triggerAfterChange: false,
|
||||
},
|
||||
});
|
||||
}],
|
||||
},
|
||||
fields: [ /* ... */ ],
|
||||
};
|
||||
```
|
||||
|
||||
## Typing context
|
||||
|
||||
The default typescript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax.
|
||||
|
||||
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
|
||||
import { RequestContext as OriginalRequestContext } from 'payload';
|
||||
|
||||
declare module 'payload' {
|
||||
// Create a new interface that merges your additional fields with the original one
|
||||
export interface RequestContext extends OriginalRequestContext {
|
||||
myObject?: string;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will add a the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.
|
||||
@@ -8,11 +8,11 @@ keywords: hooks, globals, config, configuration, documentation, Content Manageme
|
||||
|
||||
Globals feature the ability to define the following hooks:
|
||||
|
||||
- [beforeValidate](#beforeValidate)
|
||||
- [beforeChange](#beforeChange)
|
||||
- [afterChange](#afterChange)
|
||||
- [beforeRead](#beforeRead)
|
||||
- [afterRead](#afterRead)
|
||||
- [beforeValidate](#beforevalidate)
|
||||
- [beforeChange](#beforechange)
|
||||
- [afterChange](#afterchange)
|
||||
- [beforeRead](#beforeread)
|
||||
- [afterRead](#afterread)
|
||||
|
||||
## Config
|
||||
|
||||
|
||||
104
docs/integrations/vercel-visual-editing.mdx
Normal file
104
docs/integrations/vercel-visual-editing.mdx
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: Vercel Visual Editing
|
||||
label: Vercel Visual Editing
|
||||
order: 10
|
||||
desc: Payload + Vercel Visual Editing allows yours editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it.
|
||||
keywords: vercel, vercel visual editing, visual editing, content source maps, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
[Vercel Visual Editing](https://vercel.com/docs/workflow-collaboration/visual-editing) will allow your editors to navigate directly from the content rendered on your front-end to the fields in Payload that control it. This requires no changes to your front-end code and very few changes to your Payload config.
|
||||
|
||||

|
||||
|
||||
<Banner type="warning">
|
||||
Vercel Visual Editing is an enterprise-only feature and only available for
|
||||
deployments hosted on Vercel. If you are an existing enterprise customer,
|
||||
[contact our sales team](https://payloadcms.com/for-enterprise) for help with
|
||||
your integration.
|
||||
</Banner>
|
||||
|
||||
### How it works
|
||||
|
||||
To power Vercel Visual Editing, Payload embeds Content Source Maps into its API responses. Content Source Maps are invisible, encoded JSON values that include a link back to the field in the CMS that generated the content. When rendered on the page, Vercel detects and decodes these values to display the Visual Editing interface.
|
||||
|
||||
For full details on how the encoding and decoding algorithm works, check out [`@vercel/stega`](https://www.npmjs.com/package/@vercel/stega).
|
||||
|
||||
### Getting Started
|
||||
|
||||
Setting up Payload with Vercel Visual Editing is easy. First, install the `@payloadcms/plugin-csm` plugin into your project. This plugin requires an API key to install, [contact our sales team](https://payloadcms.com/for-enterprise) if you don't already have one.
|
||||
|
||||
```bash
|
||||
npm i @payloadcms/plugin-csm
|
||||
```
|
||||
|
||||
Then in the `plugins` array of your Payload config, call the plugin and enable any collections that require Content Source Maps.
|
||||
|
||||
```ts
|
||||
import { buildConfig } from "payload/config"
|
||||
import contentSourceMaps from "@payloadcms/plugin-csm"
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: "pages",
|
||||
fields: [
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'title,'
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
contentSourceMaps({
|
||||
collections: ["pages"],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
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.
|
||||
|
||||
```ts
|
||||
if (isDraftMode || process.env.VERCEL_ENV === "preview") {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_PAYLOAD_CMS_URL}/api/pages?where[slug][equals]=${slug}&encodeSourceMaps=true`
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! You are now ready to enter Edit Mode and begin visually editing your content.
|
||||
|
||||
##### Edit Mode
|
||||
|
||||
To see Visual Editing on your site, you first need to visit any preview deployment on Vercel and login using the Vercel Toolbar. When Content Source Maps are detected on the page, a pencil icon will appear in the toolbar. Clicking this icon will enable Edit Mode, highlighting all editable fields on the page in blue.
|
||||
|
||||

|
||||
|
||||
### Troubleshooting
|
||||
|
||||
##### Dates
|
||||
|
||||
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
|
||||
import { vercelStegaSplit } from "@vercel/stega";
|
||||
const { cleaned, encoded } = vercelStegaSplit(text);
|
||||
```
|
||||
|
||||
##### Blocks
|
||||
|
||||
All `blocks` fields by definition do not have plain text strings to encode. For this reason, blocks are given an additional `encodedSourceMap` key, which you can use to enable Visual Editing on entire sections of your site. You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
|
||||
|
||||
```ts
|
||||
<div data-vercel-edit-target>
|
||||
<span style={{ display: "none" }}>{encodedSourceMap}</span>
|
||||
{children}
|
||||
</div>
|
||||
```
|
||||
@@ -77,6 +77,7 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
||||
_There are more options available on an operation by operation basis outlined below._
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ Because _**you**_ are in complete control of who can do what with your data, you
|
||||
Before running in Production, you need to have built a production-ready copy of the Payload Admin panel. To do this, Payload provides the `build` NPM script. You can use it by adding a `script` to your `package.json` file like this:
|
||||
|
||||
`package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "project-name-here",
|
||||
@@ -51,7 +52,8 @@ Before running in Production, you need to have built a production-ready copy of
|
||||
},
|
||||
"dependencies": {
|
||||
// your dependencies
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, to build Payload, you would run `npm run build` in your project folder. A production-ready Admin bundle will be created in the `build` directory.
|
||||
@@ -81,16 +83,16 @@ 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.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<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:
|
||||
<a href="https://www.mongodb.com/docs/atlas/reference/free-shared-limitations/?_ga=2.176267877.1329169847.1677683154-860992573.1647438381#operational-limitations">Atlas M0 (Free Cluster), M2, and M5 Limitations</a>
|
||||
<strong>Note:</strong><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>
|
||||
|
||||
##### DocumentDB
|
||||
|
||||
When using AWS DocumentDB, you will need to configure connection options for authentication in the `mongoOptions` passed to `payload.init`. You also need to set `mongoOptions.useFacet` to `false` to disable use of the unsupported `$facet` aggregation.
|
||||
|
||||
##### CosmosDB
|
||||
|
||||
When using Azure Cosmos DB, an index is needed for any field you may want to sort on. To add the sort index for all fields that may be sorted in the admin UI use the <a href="/docs/configuration/overview">indexSortableFields</a> configuration option.
|
||||
|
||||
## File storage
|
||||
@@ -116,7 +118,7 @@ Alternatively, persistent filesystems will never delete your files and can be tr
|
||||
- Many other more traditional web hosts
|
||||
|
||||
<Banner type="error">
|
||||
<strong>Warning:</strong><br/>
|
||||
<strong>Warning:</strong><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>
|
||||
|
||||
@@ -124,7 +126,7 @@ Alternatively, persistent filesystems will never delete your files and can be tr
|
||||
|
||||
If you don't use Payload's `upload` functionality, you can go ahead and use Heroku or similar platform easily. Everything will work exactly as you want it to.
|
||||
|
||||
But, if you do, and you still want to use an ephemeral filesystem provider, you can write a hook-based solution to *copy* the files your users upload to a more permanent storage solution like Amazon S3 or DigitalOcean Spaces.
|
||||
But, if you do, and you still want to use an ephemeral filesystem provider, you can write a hook-based solution to _copy_ the files your users upload to a more permanent storage solution like Amazon S3 or DigitalOcean Spaces.
|
||||
|
||||
**To automatically send uploaded files to S3 or similar, you could:**
|
||||
|
||||
@@ -182,10 +184,9 @@ CMD ["node", "dist/server.js"]
|
||||
Here is an example of a docker-compose.yml file that can be used for development
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
payload:
|
||||
image: node:18-alpine
|
||||
ports:
|
||||
|
||||
@@ -9,7 +9,7 @@ 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.
|
||||
|
||||
<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 <a href="/docs/access-control/overview">restrict which documents certain users can access</a> 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>
|
||||
|
||||
### Simple queries
|
||||
@@ -17,59 +17,57 @@ Payload provides an extremely granular querying language through all APIs. Each
|
||||
For example, say you have a collection as follows:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from "payload/types";
|
||||
|
||||
export const Post: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
slug: "posts",
|
||||
fields: [
|
||||
{
|
||||
name: 'color',
|
||||
type: 'select',
|
||||
options: [
|
||||
'mint',
|
||||
'dark-gray',
|
||||
'white',
|
||||
],
|
||||
name: "color",
|
||||
type: "select",
|
||||
options: ["mint", "dark-gray", "white"],
|
||||
},
|
||||
{
|
||||
name: 'featured',
|
||||
type: 'checkbox',
|
||||
}
|
||||
]
|
||||
}
|
||||
name: "featured",
|
||||
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:
|
||||
|
||||
```js
|
||||
const query = {
|
||||
color: { // property name to filter on
|
||||
equals: 'mint', // operator to use and value to compare against
|
||||
color: {
|
||||
// property name to filter on
|
||||
equals: "mint", // operator to use and value to compare against
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The above example demonstrates a simple query but you can get much more complex.
|
||||
|
||||
### Operators
|
||||
|
||||
| Operator | Description |
|
||||
| -------------------- | ------------ |
|
||||
| `equals` | The value must be exactly equal. |
|
||||
| `not_equals` | The query will return all documents where the value is not equal. |
|
||||
| `greater_than` | For numeric or date-based fields. |
|
||||
| `greater_than_equal` | For numeric or date-based fields. |
|
||||
| `less_than` | For numeric or date-based fields. |
|
||||
| `less_than_equal` | For numeric or date-based fields. |
|
||||
| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. |
|
||||
| `contains` | Must contain the value entered, case-insensitive. |
|
||||
| `in` | The value must be found within the provided comma-delimited list of values. |
|
||||
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
|
||||
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
|
||||
| Operator | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `equals` | The value must be exactly equal. |
|
||||
| `not_equals` | The query will return all documents where the value is not equal. |
|
||||
| `greater_than` | For numeric or date-based fields. |
|
||||
| `greater_than_equal` | For numeric or date-based fields. |
|
||||
| `less_than` | For numeric or date-based fields. |
|
||||
| `less_than_equal` | For numeric or date-based fields. |
|
||||
| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. |
|
||||
| `contains` | Must contain the value entered, case-insensitive. |
|
||||
| `in` | The value must be found within the provided comma-delimited list of values. |
|
||||
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
|
||||
| `all` | The value must contain all values provided in the comma-delimited list. |
|
||||
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
|
||||
| `near` | For distance related to a [point field](/docs/fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
|
||||
|
||||
<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.
|
||||
</Banner>
|
||||
|
||||
@@ -79,28 +77,30 @@ In addition to defining simple queries, you can join multiple queries together u
|
||||
|
||||
```js
|
||||
const query = {
|
||||
or: [ // array of OR conditions
|
||||
or: [
|
||||
// array of OR conditions
|
||||
{
|
||||
color: {
|
||||
equals: 'mint',
|
||||
equals: "mint",
|
||||
},
|
||||
},
|
||||
{
|
||||
and: [ // nested array of AND conditions
|
||||
and: [
|
||||
// nested array of AND conditions
|
||||
{
|
||||
color: {
|
||||
equals: 'white',
|
||||
}
|
||||
equals: "white",
|
||||
},
|
||||
},
|
||||
{
|
||||
featured: {
|
||||
equals: 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,10 +111,11 @@ When working with nested properties, which can happen when using relational fiel
|
||||
|
||||
```js
|
||||
const query = {
|
||||
'artists.featured': { // nested property name to filter on
|
||||
"artists.featured": {
|
||||
// nested property name to filter on
|
||||
exists: true, // operator to use and boolean value that needs to be true
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### GraphQL Find Queries
|
||||
@@ -147,24 +148,29 @@ This one isn't too bad, but more complex queries get unavoidably more difficult
|
||||
**For example, using fetch:**
|
||||
|
||||
```js
|
||||
import qs from 'qs';
|
||||
import qs from "qs";
|
||||
|
||||
const query = {
|
||||
color: {
|
||||
equals: 'mint',
|
||||
equals: "mint",
|
||||
},
|
||||
// This query could be much more complex
|
||||
// and QS would handle it beautifully
|
||||
}
|
||||
};
|
||||
|
||||
const getPosts = async () => {
|
||||
const stringifiedQuery = qs.stringify({
|
||||
where: query // ensure that `qs` adds the `where` property, too!
|
||||
}, { addQueryPrefix: true });
|
||||
const stringifiedQuery = qs.stringify(
|
||||
{
|
||||
where: query, // ensure that `qs` adds the `where` property, too!
|
||||
},
|
||||
{ addQueryPrefix: true }
|
||||
);
|
||||
|
||||
const response = await fetch(`http://localhost:3000/api/posts${stringifiedQuery}`);
|
||||
const response = await fetch(
|
||||
`http://localhost:3000/api/posts${stringifiedQuery}`
|
||||
);
|
||||
// Continue to handle the response below...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Local API Queries
|
||||
@@ -174,27 +180,27 @@ The Local API's `find` operation accepts an object exactly how you write it. For
|
||||
```js
|
||||
const getPosts = async () => {
|
||||
const posts = await payload.find({
|
||||
collection: 'posts',
|
||||
collection: "posts",
|
||||
where: {
|
||||
color: {
|
||||
equals: 'mint',
|
||||
equals: "mint",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return posts;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Sort
|
||||
|
||||
Payload `find` queries support a `sort` parameter through all APIs. Pass the `name` of a top-level field to sort by that field in ascending order. Prefix the name of the field with a minus symbol ("-") to sort in descending order.
|
||||
Payload `find` queries support a `sort` parameter through all APIs. Pass the `name` of a top-level field to sort by that field in ascending order. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Because sorting is handled by the database, the field you wish to sort on must be stored in the database to work; not a [virtual field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges). It is recommended to enable indexing for the fields where sorting is used.
|
||||
|
||||
**REST example:**
|
||||
**`https://localhost:3000/api/posts?sort=-createdAt`**
|
||||
|
||||
**GraphQL example:**
|
||||
|
||||
```
|
||||
query {
|
||||
Posts(sort: "-createdAt") {
|
||||
@@ -210,10 +216,10 @@ query {
|
||||
```js
|
||||
const getPosts = async () => {
|
||||
const posts = await payload.find({
|
||||
collection: 'posts',
|
||||
sort: '-createdAt',
|
||||
collection: "posts",
|
||||
sort: "-createdAt",
|
||||
});
|
||||
|
||||
return posts;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -7,7 +7,8 @@ keywords: rest, api, documentation, Content Management System, cms, headless, ja
|
||||
---
|
||||
|
||||
<Banner>
|
||||
A fully functional REST API is automatically generated from your Collection and Global configs.
|
||||
A fully functional REST API is automatically generated from your Collection
|
||||
and Global configs.
|
||||
</Banner>
|
||||
|
||||
All Payload API routes are mounted prefixed to your config's `routes.api` URL segment (default: `/api`).
|
||||
@@ -26,58 +27,534 @@ Note: Collection slugs must be formatted in kebab-case
|
||||
|
||||
**All CRUD operations are exposed as follows:**
|
||||
|
||||
| Method | Path | Description |
|
||||
|----------|-------------------------------|--------------------------------------------------|
|
||||
| `GET` | `/api/{collection-slug}` | Find paginated documents |
|
||||
| `GET` | `/api/{collection-slug}/:id` | Find a specific document by ID |
|
||||
| `POST` | `/api/{collection-slug}` | Create a new document |
|
||||
| `PATCH` | `/api/{collection-slug}` | Update all documents matching the `where` query |
|
||||
| `PATCH` | `/api/{collection-slug}/:id` | Update a document by ID |
|
||||
| `DELETE` | `/api/{collection-slug}` | Delete all documents matching the `where` query |
|
||||
| `DELETE` | `/api/{collection-slug}/:id` | Delete an existing document by ID |
|
||||
<RestExamples
|
||||
data={[
|
||||
{
|
||||
operation: "Find",
|
||||
method: "GET",
|
||||
path: "/api/{collection-slug}",
|
||||
description: "Find paginated documents",
|
||||
example: {
|
||||
slug: "getCollection",
|
||||
req: true,
|
||||
res: {
|
||||
paginated: true,
|
||||
data: {
|
||||
id: "644a5c24cc1383022535fc7c",
|
||||
title: "Home",
|
||||
content: "REST API examples",
|
||||
slug: "home",
|
||||
createdAt: "2023-04-27T11:27:32.419Z",
|
||||
updatedAt: "2023-04-27T11:27:32.419Z",
|
||||
},
|
||||
},
|
||||
drawerContent: (
|
||||
<>
|
||||
<h6>Additional <code>find</code> query parameters</h6>
|
||||
The <code>find</code> endpoint supports the following additional query parameters:
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/docs/queries/overview#sort">sort</a> - sort by field
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/queries/overview">where</a> - pass a where query to constrain returned
|
||||
documents
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/queries/pagination#pagination-controls">limit</a> - limit the returned
|
||||
documents to a certain number
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/queries/pagination#pagination-controls">page</a> - get a specific page of
|
||||
documents
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Find By ID",
|
||||
method: "GET",
|
||||
path: "/api/{collection-slug}/{id}",
|
||||
description: "Find a specific document by ID",
|
||||
example: {
|
||||
slug: "findByID",
|
||||
req: true,
|
||||
res: {
|
||||
id: "644a5c24cc1383022535fc7c",
|
||||
title: "Home",
|
||||
content: "REST API examples",
|
||||
slug: "home",
|
||||
createdAt: "2023-04-27T11:27:32.419Z",
|
||||
updatedAt: "2023-04-27T11:27:32.419Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Create",
|
||||
method: "POST",
|
||||
path: "/api/{collection-slug}",
|
||||
description: "Create a new document",
|
||||
example: {
|
||||
slug: "createDocument",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
body: {
|
||||
title: "New page",
|
||||
content: "Here is some content",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Page successfully created.",
|
||||
doc: {
|
||||
id: "644ba34c86359864f9535932",
|
||||
title: "New page",
|
||||
content: "Here is some content",
|
||||
slug: "new-page",
|
||||
createdAt: "2023-04-28T10:43:24.466Z",
|
||||
updatedAt: "2023-04-28T10:43:24.466Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Update",
|
||||
method: "PATCH",
|
||||
path: "/api/{collection-slug}",
|
||||
description: "Update all documents matching the where query",
|
||||
example: {
|
||||
slug: "updateDocument",
|
||||
req: {
|
||||
credentials: true,
|
||||
query: true,
|
||||
headers: true,
|
||||
body: {
|
||||
title: "I have been updated!",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
docs: [
|
||||
{
|
||||
id: "644ba34c86359864f9535932",
|
||||
title: "I have been updated!",
|
||||
content: "Here is some content",
|
||||
slug: "new-page",
|
||||
createdAt: "2023-04-28T10:43:24.466Z",
|
||||
updatedAt: "2023-04-28T10:45:23.724Z",
|
||||
},
|
||||
],
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Update By ID",
|
||||
method: "PATCH",
|
||||
path: "/api/{collection-slug}/{id}",
|
||||
description: "Update a document by ID",
|
||||
example: {
|
||||
slug: "updateDocumentByID",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
body: {
|
||||
title: "I have been updated by ID!",
|
||||
categories: "example-uuid",
|
||||
tags: {
|
||||
relationTo: "location",
|
||||
value: "another-example-uuid",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Updated successfully.",
|
||||
doc: {
|
||||
id: "644a5c24cc1383022535fc7c",
|
||||
title: "I have been updated by ID!",
|
||||
content: "REST API examples",
|
||||
categories: {
|
||||
id: "example-uuid",
|
||||
name: "Test Category",
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
relationTo: "location",
|
||||
value: {
|
||||
id: "another-example-uuid",
|
||||
name: "Test Location",
|
||||
},
|
||||
},
|
||||
],
|
||||
slug: "home",
|
||||
createdAt: "2023-04-27T11:27:32.419Z",
|
||||
updatedAt: "2023-04-28T10:47:59.259Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Delete",
|
||||
method: "DELETE",
|
||||
path: "/api/{collection-slug}",
|
||||
description: "Delete all documents matching the where query",
|
||||
example: {
|
||||
slug: "deleteDocuments",
|
||||
req: {
|
||||
credentials: true,
|
||||
query: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
docs: [
|
||||
{
|
||||
id: "644ba4cf86359864f953594b",
|
||||
title: "New page",
|
||||
content: "Here is some content",
|
||||
slug: "new-page",
|
||||
createdAt: "2023-04-28T10:49:51.359Z",
|
||||
updatedAt: "2023-04-28T10:49:51.359Z",
|
||||
},
|
||||
],
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Delete by ID",
|
||||
method: "DELETE",
|
||||
path: "/api/{collection-slug}/{id}",
|
||||
description: "Delete an existing document by ID",
|
||||
example: {
|
||||
slug: "deleteByID",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
id: "644ba51786359864f9535954",
|
||||
title: "New page",
|
||||
content: "Here is some content",
|
||||
slug: "new-page",
|
||||
createdAt: "2023-04-28T10:51:03.028Z",
|
||||
updatedAt: "2023-04-28T10:51:03.028Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
##### Additional `find` query parameters
|
||||
|
||||
The `find` endpoint supports the following additional query parameters:
|
||||
|
||||
- [sort](/docs/queries/overview#sort) - sort by field
|
||||
- [where](/docs/queries/overview) - pass a `where` query to constrain returned documents
|
||||
- [limit](/docs/queries/pagination#pagination-controls) - limit the returned documents to a certain number
|
||||
- [page](/docs/queries/pagination#pagination-controls) - get a specific page of documents
|
||||
]}
|
||||
/>
|
||||
|
||||
## Auth Operations
|
||||
|
||||
Auth enabled collections are also given the following endpoints:
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | --------------------------- | ----------- |
|
||||
| `POST` | `/api/{collection-slug}/verify/:token` | [Email verification](/docs/authentication/operations#verify-by-email), if enabled. |
|
||||
| `POST` | `/api/{collection-slug}/unlock` | [Unlock a user's account](/docs/authentication/operations#unlock), if enabled. |
|
||||
| `POST` | `/api/{collection-slug}/login` | [Logs in](/docs/authentication/operations#login) a user with email / password. |
|
||||
| `POST` | `/api/{collection-slug}/logout` | [Logs out](/docs/authentication/operations#logout) a user. |
|
||||
| `POST` | `/api/{collection-slug}/refresh-token` | [Refreshes a token](/docs/authentication/operations#refresh) that has not yet expired. |
|
||||
| `GET` | `/api/{collection-slug}/me` | [Returns the currently logged in user with token](/docs/authentication/operations#me). |
|
||||
| `POST` | `/api/{collection-slug}/forgot-password` | [Password reset workflow](/docs/authentication/operations#forgot-password) entry point. |
|
||||
| `POST` | `/api/{collection-slug}/reset-password` | [To reset the user's password](/docs/authentication/operations#reset-password). |
|
||||
<RestExamples
|
||||
data={[
|
||||
{
|
||||
operation: "Login",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/login",
|
||||
description: "Logs in a user with email / password",
|
||||
example: {
|
||||
slug: "login",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
body: {
|
||||
email: "dev@payloadcms.com",
|
||||
password: "password",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Auth Passed",
|
||||
user: {
|
||||
id: "644b8453cd20c7857da5a9b0",
|
||||
email: "dev@payloadcms.com",
|
||||
_verified: true,
|
||||
createdAt: "2023-04-28T08:31:15.788Z",
|
||||
updatedAt: "2023-04-28T11:11:03.716Z",
|
||||
},
|
||||
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
exp: 1682689147,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Logout",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/logout",
|
||||
description: "Logs out a user",
|
||||
example: {
|
||||
slug: "logout",
|
||||
req: {
|
||||
headers: true,
|
||||
credentials: true,
|
||||
},
|
||||
res: {
|
||||
message: "You have been logged out successfully.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Unlock",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/unlock",
|
||||
description: "Unlock a user account",
|
||||
example: {
|
||||
slug: "unlockCollection",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
body: {
|
||||
email: "dev@payloadcms.com",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Success",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Refresh",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/refresh-token",
|
||||
description: "Refreshes a token that has not yet expired",
|
||||
example: {
|
||||
slug: "refreshToken",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
message: "Token refresh successful",
|
||||
refreshedToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
exp: 1682689362,
|
||||
user: {
|
||||
email: "dev@payloadcms.com",
|
||||
id: "644b8453cd20c7857da5a9b0",
|
||||
collection: "users",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Verify User",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/verify/{token}",
|
||||
description: "User verification",
|
||||
example: {
|
||||
slug: "verifyUser",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
message: "Email verified successfully.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Current User",
|
||||
method: "GET",
|
||||
path: "/api/{user-collection}/me",
|
||||
description: "Returns the currently logged in user with token",
|
||||
example: {
|
||||
slug: "currentUser",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
user: {
|
||||
id: "644b8453cd20c7857da5a9b0",
|
||||
email: "dev@payloadcms.com",
|
||||
_verified: true,
|
||||
createdAt: "2023-04-28T08:31:15.788Z",
|
||||
updatedAt: "2023-04-28T11:45:23.926Z",
|
||||
_strategy: "local-jwt",
|
||||
},
|
||||
collection: "users",
|
||||
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
exp: 1682689523,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Forgot Password",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/forgot-password",
|
||||
description: "Password reset workflow entry point",
|
||||
example: {
|
||||
slug: "forgotPassword",
|
||||
req: {
|
||||
headers: true,
|
||||
credentials: true,
|
||||
body: {
|
||||
email: "dev@payloadcms.com",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Success",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Reset Password",
|
||||
method: "POST",
|
||||
path: "/api/{user-collection}/reset-password",
|
||||
description: "Reset user password",
|
||||
example: {
|
||||
slug: "resetPassword",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
body: {
|
||||
token: "7eac3830ffcfc7f9f66c00315dabeb11575dba91",
|
||||
password: "newPassword",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Password reset successfully.",
|
||||
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
|
||||
user: {
|
||||
id: "644baa473ea9538765cc30fc",
|
||||
email: "dev@payloadcms.com",
|
||||
_verified: true,
|
||||
createdAt: "2023-04-28T11:13:11.569Z",
|
||||
updatedAt: "2023-04-28T11:49:23.860Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
]}
|
||||
/>
|
||||
|
||||
## Globals
|
||||
|
||||
Globals cannot be created or deleted, so there are only two REST endpoints opened:
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | --------------------------- | ----------------------- |
|
||||
| `GET` | `/api/globals/{globalSlug}` | Get a global by slug |
|
||||
| `POST` | `/api/globals/{globalSlug}` | Update a global by slug |
|
||||
<RestExamples
|
||||
data={[
|
||||
{
|
||||
operation: "Get Global",
|
||||
method: "GET",
|
||||
path: "/api/globals/{global-slug}",
|
||||
description: "Get a global by slug",
|
||||
example: {
|
||||
slug: "getGlobal",
|
||||
req: {
|
||||
credentials: true,
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
announcement: "Here is an announcement!",
|
||||
globalType: "announcement",
|
||||
createdAt: "2023-04-28T08:53:56.066Z",
|
||||
updatedAt: "2023-04-28T08:53:56.066Z",
|
||||
id: "644b89a496c64a833fe579c9",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Update Global",
|
||||
method: "POST",
|
||||
path: "/api/globals/{global-slug}",
|
||||
description: "Update a global by slug",
|
||||
example: {
|
||||
slug: "updateGlobal",
|
||||
req: {
|
||||
headers: true,
|
||||
credentials: true,
|
||||
body: {
|
||||
announcement: "Paging Doctor Scrunt",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
announcement: "Paging Doctor Scrunt",
|
||||
globalType: "announcement",
|
||||
createdAt: "2023-04-28T08:53:56.066Z",
|
||||
updatedAt: "2023-04-28T08:53:56.066Z",
|
||||
id: "644b89a496c64a833fe579c9",
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
## Preferences
|
||||
|
||||
In addition to the dynamically generated endpoints above Payload also has REST endpoints to manage the admin user [preferences](/docs/admin/overview#preferences) for data specific to the authenticated user.
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | --------------------------- | ----------------------- |
|
||||
| `GET` | `/api/_preferences/{key}` | Get a preference by key |
|
||||
| `POST` | `/api/_preferences/{key}` | Create or update by key |
|
||||
| `DELETE` | `/api/_preferences/{key}` | Delete a user preference by key |
|
||||
<RestExamples
|
||||
data={[
|
||||
{
|
||||
operation: "Get Preference",
|
||||
method: "GET",
|
||||
path: "/api/_preferences/{key}",
|
||||
description: "Get a preference by key",
|
||||
example: {
|
||||
slug: "getPreference",
|
||||
req: {
|
||||
headers: true,
|
||||
credentials: true,
|
||||
},
|
||||
res: {
|
||||
_id: "644bb7a8307b3d363c6edf2c",
|
||||
key: "region",
|
||||
user: "644b8453cd20c7857da5a9b0",
|
||||
userCollection: "users",
|
||||
__v: 0,
|
||||
createdAt: "2023-04-28T12:10:16.689Z",
|
||||
updatedAt: "2023-04-28T12:10:16.689Z",
|
||||
value: "Europe/London",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Create Preference",
|
||||
method: "POST",
|
||||
path: "/api/_preferences/{key}",
|
||||
description: "Create or update a preference by key",
|
||||
example: {
|
||||
slug: "createPreference",
|
||||
req: {
|
||||
headers: true,
|
||||
credentials: true,
|
||||
body: {
|
||||
value: "Europe/London",
|
||||
},
|
||||
},
|
||||
res: {
|
||||
message: "Updated successfully.",
|
||||
doc: {
|
||||
user: "644b8453cd20c7857da5a9b0",
|
||||
key: "region",
|
||||
userCollection: "users",
|
||||
value: "Europe/London",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Delete Preference",
|
||||
method: "DELETE",
|
||||
path: "/api/_preferences/{key}",
|
||||
description: "Delete a preference by key",
|
||||
example: {
|
||||
slug: "deletePreference",
|
||||
req: {
|
||||
headers: true,
|
||||
},
|
||||
res: {
|
||||
message: "deletedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
## Custom Endpoints
|
||||
|
||||
@@ -85,43 +562,48 @@ Additional REST API endpoints can be added to your application by providing an a
|
||||
|
||||
Each endpoint object needs to have:
|
||||
|
||||
| Property | Description |
|
||||
| ---------- | ----------------------------------------- |
|
||||
| **`path`** | A string for the endpoint route after the collection or globals slug |
|
||||
| **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' |
|
||||
| **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) |
|
||||
| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Property | Description |
|
||||
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`path`** | A string for the endpoint route after the collection or globals slug |
|
||||
| **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' |
|
||||
| **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) |
|
||||
| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
Example:
|
||||
|
||||
```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
|
||||
export const Orders: CollectionConfig = {
|
||||
slug: 'orders',
|
||||
fields: [ /* ... */ ],
|
||||
slug: "orders",
|
||||
fields: [
|
||||
/* ... */
|
||||
],
|
||||
// highlight-start
|
||||
endpoints: [
|
||||
{
|
||||
path: '/:id/tracking',
|
||||
method: 'get',
|
||||
path: "/:id/tracking",
|
||||
method: "get",
|
||||
handler: async (req, res, next) => {
|
||||
const tracking = await getTrackingInfo(req.params.id);
|
||||
if (tracking) {
|
||||
res.status(200).send({ tracking });
|
||||
} else {
|
||||
res.status(404).send({ error: 'not found' });
|
||||
res.status(404).send({ error: "not found" });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong><br/>
|
||||
**req** will have the **payload** object and can be used inside your endpoint handlers for making calls like req.payload.find() that will make use of access control and hooks.
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
**req** will have the **payload** object and can be used inside your endpoint
|
||||
handlers for making calls like req.payload.find() that will make use of access
|
||||
control and hooks.
|
||||
</Banner>
|
||||
|
||||
25
docs/troubleshooting/troubleshooting.mdx
Normal file
25
docs/troubleshooting/troubleshooting.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
label: Troubleshooting
|
||||
order: 10
|
||||
desc: Troubleshooting Common Issues in Payload
|
||||
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, express, troubleshooting
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "Unauthorized, you must be logged in to make this request" when attempting to log in
|
||||
|
||||
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.
|
||||
- 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
|
||||
|
||||
This error likely means that the auth cookie that Payload sets after logging in successfully is being rejected because of misconfiguration.
|
||||
|
||||
To further investigate the issue:
|
||||
|
||||
- Go to the login screen. Open your inspector. Go to the Network tab.
|
||||
- Log in and then find the login request that should appear in your network panel. Click the login request.
|
||||
- The login request should have a Set-Cookie header on the response, and the cookie should be getting set successfully. If it is not, most browsers generally have a little yellow ⚠️ symbol that you can hover over to see why the cookie was rejected.
|
||||
@@ -8,7 +8,9 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
|
||||
|
||||
While building your own custom functionality into Payload, like plugins, hooks, access control functions, custom routes, GraphQL queries / mutations, or anything else, you may benefit from generating your own TypeScript types dynamically from your Payload config itself.
|
||||
|
||||
Run the following command in a Payload project to generate types:
|
||||
### Types generatation script
|
||||
|
||||
Run the following command in a Payload project to generate types based on your Payload config:
|
||||
|
||||
```
|
||||
payload generate:types
|
||||
@@ -16,6 +18,46 @@ payload generate:types
|
||||
|
||||
You can run this command whenever you need to regenerate your types, and then you can use these types in your Payload code directly.
|
||||
|
||||
### Configuration
|
||||
|
||||
In order for Payload to properly infer these types when using local operations, you'll need to alias the following in your tsconfig.json file:
|
||||
|
||||
```json
|
||||
// tsconfig.json
|
||||
|
||||
{
|
||||
"compilerOptions": {
|
||||
// ...
|
||||
"paths": {
|
||||
"payload/generated-types": [
|
||||
"./src/payload-types.ts" // Ensure this matches the path to your typescript outputFile
|
||||
]
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom output file path
|
||||
|
||||
You can specify where you want your types to be generated by adding a property to your Payload config:
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
|
||||
{
|
||||
// ...
|
||||
typescript: {
|
||||
// defaults to: path.resolve(__dirname, './payload-types.ts')
|
||||
outputFile: path.resolve(__dirname, './generated-types.ts'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The above example places your types next to your Payload config itself as the file `generated-types.ts`.
|
||||
|
||||
### Example Usage
|
||||
|
||||
For example, let's look at the following simple Payload config:
|
||||
|
||||
```ts
|
||||
@@ -74,44 +116,65 @@ export interface Post {
|
||||
title?: string;
|
||||
author?: string | User;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In order for Payload to properly infer these types when using local operations, you'll need to alias the following in your tsconfig.json file:
|
||||
### Custom Field Interfaces
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
// ...
|
||||
"paths": {
|
||||
"payload/generated-types": [
|
||||
"./src/payload-types.ts", // Ensure this matches the path to your typescript outputFile
|
||||
],
|
||||
}
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Customizing the output path of your generated types
|
||||
|
||||
You can specify where you want your types to be generated by adding a property to your Payload config:
|
||||
For `array`, `block`, `group` and named `tab` fields, you can generate top level reusable interfaces. The following group field config:
|
||||
|
||||
```ts
|
||||
{
|
||||
// the remainder of your config
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, './payload-types.ts'),
|
||||
},
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
interfaceName: 'SharedMeta', <-- here!!
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The above example places your types next to your Payload config itself as the file `generated-types.ts`. By default, the file will be output to your current working directory as `payload-types.ts`.
|
||||
will generate:
|
||||
|
||||
```ts
|
||||
// a top level reusable interface!!
|
||||
export interface SharedMeta {
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// example usage inside collection interface
|
||||
export interface Collection1 {
|
||||
// ...other fields
|
||||
meta?: SharedMeta;
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Naming Collisions</strong>
|
||||
<br />
|
||||
Since these types are hoisted to the top level, you need to be aware that
|
||||
naming collisions can occur. For example, if you have a collection with the
|
||||
name of `Meta` and you also create a interface with the name `Meta` they will
|
||||
collide. It is recommended to scope your interfaces by appending the field
|
||||
type to the end, i.e. `MetaGroup` or similar.
|
||||
</Banner>
|
||||
|
||||
### Using your types
|
||||
|
||||
Now that your types have been generated, payloads local API will now be typed. It is common for users to want to use this in their frontend code, we recommend generating them with payload and then copying the file over to your frontend codebase. This is the simplest way to get your types into your frontend codebase.
|
||||
|
||||
#### Adding an NPM script
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br/>
|
||||
<strong>Important</strong>
|
||||
<br />
|
||||
Payload needs to be able to find your config to generate your types.
|
||||
</Banner>
|
||||
|
||||
|
||||
@@ -190,9 +190,7 @@ export const Media: CollectionConfig = {
|
||||
<Banner>
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If you specify a function to return an admin thumbnail, but your upload is not
|
||||
an image file type (for example, PDF or TXT) your function will not be used.
|
||||
Instead, Payload will display its generic file upload graphic.
|
||||
This function runs in the browser. If your function returns `null` or `false` Payload will show the default generic file thumbnail instead.
|
||||
</Banner>
|
||||
|
||||
### MimeTypes
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
PAYLOAD_PUBLIC_SITE_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||
MONGODB_URI=mongodb://localhost/payload-example-auth
|
||||
PAYLOAD_SECRET=PAYLOAD_AUTH_EXAMPLE_SECRET_KEY
|
||||
COOKIE_DOMAIN=localhost
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload CMS.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
users: User
|
||||
}
|
||||
globals: {}
|
||||
}
|
||||
export interface User {
|
||||
id: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
roles?: Array<'admin' | 'user'>
|
||||
email?: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
password?: string
|
||||
}
|
||||
1
examples/auth/next-app/.env.example
Normal file
1
examples/auth/next-app/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_CMS_URL=http://localhost:3000
|
||||
7
examples/auth/next-app/.eslintrc.js
Normal file
7
examples/auth/next-app/.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['plugin:@next/next/recommended', '@payloadcms'],
|
||||
rules: {
|
||||
'import/extensions': 'off',
|
||||
},
|
||||
}
|
||||
41
examples/auth/next-app/README.md
Normal file
41
examples/auth/next-app/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Payload Auth Example Front-End
|
||||
|
||||
This is a [Payload](https://payloadcms.com) + [Next.js](https://nextjs.org) app using the [App Router](https://nextjs.org/docs/app) made explicitly for the [Payload Auth Example](https://github.com/payloadcms/payload/tree/master/examples/auth). It demonstrates how to authenticate your Next.js app using [Payload Authentication](https://payloadcms.com/docs/authentication/overview).
|
||||
|
||||
> This example uses the App Router, the latest API of Next.js. If your app is using the legacy [Pages Router](https://nextjs.org/docs/pages), check out the official [Pages Router Example](https://github.com/payloadcms/payload/tree/master/examples/auth/next-pages).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Payload
|
||||
|
||||
First you'll need a running Payload app. There is one made explicitly for this example and [can be found here](https://github.com/payloadcms/payload/tree/master/examples/auth/payload). If you have not done so already, clone it down and follow the setup instructions there. This will provide all the necessary APIs that your Next.js app requires for authentication.
|
||||
|
||||
### Next.js
|
||||
|
||||
1. Clone this repo
|
||||
2. `cd` into this directory and run `yarn` or `npm install`
|
||||
3. `cp .env.example .env` to copy the example environment variables
|
||||
4. `yarn dev` or `npm run dev` to start the server
|
||||
5. `open http://localhost:3001` to see the result
|
||||
|
||||
Once running, a user is automatically seeded in your local environment with some basic instructions. See the [Payload Auth Example](https://github.com/payloadcms/payload/tree/master/examples/auth) for full details.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Payload and Next.js, take a look at the following resources:
|
||||
|
||||
- [Payload Documentation](https://payloadcms.com/docs) - learn about Payload features and API.
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Payload GitHub repository](https://github.com/payloadcms/payload) as well as [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deployment
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new) from the creators of Next.js. You could also combine this app into a [single Express server](https://github.com/payloadcms/payload/tree/master/examples/custom-server) and deploy in to [Payload Cloud](https://payloadcms.com/new/import).
|
||||
|
||||
Check out our [Payload deployment documentation](https://payloadcms.com/docs/production/deployment) or the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
## Questions
|
||||
|
||||
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
|
||||
@@ -0,0 +1,40 @@
|
||||
@import '../../_css/type.scss';
|
||||
|
||||
.button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.label {
|
||||
@extend %label;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.appearance--primary {
|
||||
background-color: var(--theme-elevation-1000);
|
||||
color: var(--theme-elevation-0);
|
||||
}
|
||||
|
||||
.appearance--secondary {
|
||||
background-color: transparent;
|
||||
box-shadow: inset 0 0 0 1px var(--theme-elevation-1000);
|
||||
}
|
||||
|
||||
.appearance--default {
|
||||
padding: 0;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
75
examples/auth/next-app/app/_components/Button/index.tsx
Normal file
75
examples/auth/next-app/app/_components/Button/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client'
|
||||
|
||||
import React, { ElementType } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export type Props = {
|
||||
label?: string
|
||||
appearance?: 'default' | 'primary' | 'secondary'
|
||||
el?: 'button' | 'link' | 'a'
|
||||
onClick?: () => void
|
||||
href?: string
|
||||
newTab?: boolean
|
||||
className?: string
|
||||
type?: 'submit' | 'button'
|
||||
disabled?: boolean
|
||||
invert?: boolean
|
||||
}
|
||||
|
||||
export const Button: React.FC<Props> = ({
|
||||
el: elFromProps = 'link',
|
||||
label,
|
||||
newTab,
|
||||
href,
|
||||
appearance,
|
||||
className: classNameFromProps,
|
||||
onClick,
|
||||
type = 'button',
|
||||
disabled,
|
||||
invert,
|
||||
}) => {
|
||||
let el = elFromProps
|
||||
const newTabProps = newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {}
|
||||
|
||||
const className = [
|
||||
classes.button,
|
||||
classNameFromProps,
|
||||
classes[`appearance--${appearance}`],
|
||||
invert && classes[`${appearance}--invert`],
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
const content = (
|
||||
<div className={classes.content}>
|
||||
<span className={classes.label}>{label}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (onClick || type === 'submit') el = 'button'
|
||||
|
||||
if (el === 'link') {
|
||||
return (
|
||||
<Link href={href || ''} className={className} {...newTabProps} onClick={onClick}>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const Element: ElementType = el
|
||||
|
||||
return (
|
||||
<Element
|
||||
href={href}
|
||||
className={className}
|
||||
type={type}
|
||||
{...newTabProps}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{content}
|
||||
</Element>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.gutter {
|
||||
max-width: 1920px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.gutterLeft {
|
||||
padding-left: var(--gutter-h);
|
||||
}
|
||||
|
||||
.gutterRight {
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
@@ -16,7 +16,12 @@ export const Gutter: React.FC<Props> = forwardRef<HTMLDivElement, Props>((props,
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={[left && classes.gutterLeft, right && classes.gutterRight, className]
|
||||
className={[
|
||||
classes.gutter,
|
||||
left && classes.gutterLeft,
|
||||
right && classes.gutterRight,
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
@@ -0,0 +1,20 @@
|
||||
@use '../../../_css/queries.scss' as *;
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: calc(var(--base) / 4) var(--base);
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
opacity: 1;
|
||||
transition: opacity 100ms linear;
|
||||
visibility: visible;
|
||||
|
||||
> * {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
38
examples/auth/next-app/app/_components/Header/Nav/index.tsx
Normal file
38
examples/auth/next-app/app/_components/Header/Nav/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useAuth } from '../../../_providers/Auth'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const HeaderNav: React.FC = () => {
|
||||
const { user } = useAuth()
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={[
|
||||
classes.nav,
|
||||
// fade the nav in on user load to avoid flash of content and layout shift
|
||||
// Vercel also does this in their own website header, see https://vercel.com
|
||||
user === undefined && classes.hide,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{user && (
|
||||
<React.Fragment>
|
||||
<Link href="/account">Account</Link>
|
||||
<Link href="/logout">Logout</Link>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{!user && (
|
||||
<React.Fragment>
|
||||
<Link href="/login">Login</Link>
|
||||
<Link href="/create-account">Create Account</Link>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@use '../../_css/queries.scss' as *;
|
||||
|
||||
.header {
|
||||
padding: var(--base) 0;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: calc(var(--base) / 2) var(--base);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
:global([data-theme="light"]) {
|
||||
.logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
34
examples/auth/next-app/app/_components/Header/index.tsx
Normal file
34
examples/auth/next-app/app/_components/Header/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { Gutter } from '../Gutter'
|
||||
import { HeaderNav } from './Nav'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link href="/" className={classes.logo}>
|
||||
<picture>
|
||||
<source
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-light.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<Image
|
||||
width={150}
|
||||
height={30}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/master/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
<HeaderNav />
|
||||
</Gutter>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
@@ -0,0 +1,56 @@
|
||||
@import '../../_css/common';
|
||||
|
||||
.inputWrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
font-family: system-ui;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
background: none;
|
||||
background-color: var(--theme-elevation-100);
|
||||
color: var(--theme-elevation-1000);
|
||||
height: calc(var(--base) * 2);
|
||||
line-height: calc(var(--base) * 2);
|
||||
padding: 0 calc(var(--base) / 2);
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:-webkit-autofill,
|
||||
&:-webkit-autofill:hover,
|
||||
&:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: var(--theme-text);
|
||||
-webkit-box-shadow: 0 0 0px 1000px var(--theme-elevation-150) inset;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.input {
|
||||
background-color: var(--theme-elevation-150);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: var(--theme-error-150);
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
margin-bottom: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
font-size: small;
|
||||
line-height: 1.25;
|
||||
margin-top: 4px;
|
||||
color: red;
|
||||
}
|
||||
55
examples/auth/next-app/app/_components/Input/index.tsx
Normal file
55
examples/auth/next-app/app/_components/Input/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { FieldValues, UseFormRegister, Validate } from 'react-hook-form'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
type Props = {
|
||||
name: string
|
||||
label: string
|
||||
register: UseFormRegister<FieldValues & any>
|
||||
required?: boolean
|
||||
error: any
|
||||
type?: 'text' | 'number' | 'password' | 'email'
|
||||
validate?: (value: string) => boolean | string
|
||||
}
|
||||
|
||||
export const Input: React.FC<Props> = ({
|
||||
name,
|
||||
label,
|
||||
required,
|
||||
register,
|
||||
error,
|
||||
type = 'text',
|
||||
validate,
|
||||
}) => {
|
||||
return (
|
||||
<div className={classes.inputWrap}>
|
||||
<label htmlFor="name" className={classes.label}>
|
||||
{`${label} ${required ? '*' : ''}`}
|
||||
</label>
|
||||
<input
|
||||
className={[classes.input, error && classes.error].filter(Boolean).join(' ')}
|
||||
{...{ type }}
|
||||
{...register(name, {
|
||||
required,
|
||||
validate,
|
||||
...(type === 'email'
|
||||
? {
|
||||
pattern: {
|
||||
value: /\S+@\S+\.\S+/,
|
||||
message: 'Please enter a valid email',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
})}
|
||||
/>
|
||||
{error && (
|
||||
<div className={classes.errorMessage}>
|
||||
{!error?.message && error?.type === 'required'
|
||||
? 'This field is required'
|
||||
: error?.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
@import '../../_css/common';
|
||||
|
||||
.message {
|
||||
padding: calc(var(--base) / 2) calc(var(--base) / 2);
|
||||
line-height: 1.25;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.default {
|
||||
background-color: var(--theme-elevation-100);
|
||||
color: var(--theme-elevation-1000);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--theme-warning-500);
|
||||
color: var(--theme-warning-900);
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: var(--theme-error-500);
|
||||
color: var(--theme-error-900);
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--theme-success-500);
|
||||
color: var(--theme-success-900);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.default {
|
||||
background-color: var(--theme-elevation-900);
|
||||
color: var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--theme-warning-100);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--theme-error-100);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--theme-success-100);
|
||||
}
|
||||
}
|
||||
33
examples/auth/next-app/app/_components/Message/index.tsx
Normal file
33
examples/auth/next-app/app/_components/Message/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const Message: React.FC<{
|
||||
message?: React.ReactNode
|
||||
error?: React.ReactNode
|
||||
success?: React.ReactNode
|
||||
warning?: React.ReactNode
|
||||
className?: string
|
||||
}> = ({ message, error, success, warning, className }) => {
|
||||
const messageToRender = message || error || success || warning
|
||||
|
||||
if (messageToRender) {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
classes.message,
|
||||
className,
|
||||
error && classes.error,
|
||||
success && classes.success,
|
||||
warning && classes.warning,
|
||||
!error && !success && !warning && classes.default,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{messageToRender}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
import { Message } from '../Message'
|
||||
|
||||
export const RenderParams: React.FC<{
|
||||
params?: string[]
|
||||
message?: string
|
||||
className?: string
|
||||
}> = ({ params = ['error', 'message', 'success'], message, className }) => {
|
||||
const searchParams = useSearchParams()
|
||||
const paramValues = params.map(param => searchParams.get(param)).filter(Boolean)
|
||||
|
||||
if (paramValues.length) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{paramValues.map(paramValue => (
|
||||
<Message
|
||||
key={paramValue}
|
||||
message={(message || 'PARAM')?.replace('PARAM', paramValue || '')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.richText {
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import escapeHTML from 'escape-html'
|
||||
import { Text } from 'slate'
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
type Children = Leaf[]
|
||||
|
||||
type Leaf = {
|
||||
type: string
|
||||
value?: {
|
||||
url: string
|
||||
alt: string
|
||||
}
|
||||
children: Children
|
||||
url?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const serialize = (children: Children): React.ReactNode[] =>
|
||||
children.map((node, i) => {
|
||||
if (Text.isText(node)) {
|
||||
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
|
||||
|
||||
if (node.bold) {
|
||||
text = <strong key={i}>{text}</strong>
|
||||
}
|
||||
|
||||
if (node.code) {
|
||||
text = <code key={i}>{text}</code>
|
||||
}
|
||||
|
||||
if (node.italic) {
|
||||
text = <em key={i}>{text}</em>
|
||||
}
|
||||
|
||||
if (node.underline) {
|
||||
text = (
|
||||
<span style={{ textDecoration: 'underline' }} key={i}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (node.strikethrough) {
|
||||
text = (
|
||||
<span style={{ textDecoration: 'line-through' }} key={i}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return <Fragment key={i}>{text}</Fragment>
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'h1':
|
||||
return <h1 key={i}>{serialize(node.children)}</h1>
|
||||
case 'h2':
|
||||
return <h2 key={i}>{serialize(node.children)}</h2>
|
||||
case 'h3':
|
||||
return <h3 key={i}>{serialize(node.children)}</h3>
|
||||
case 'h4':
|
||||
return <h4 key={i}>{serialize(node.children)}</h4>
|
||||
case 'h5':
|
||||
return <h5 key={i}>{serialize(node.children)}</h5>
|
||||
case 'h6':
|
||||
return <h6 key={i}>{serialize(node.children)}</h6>
|
||||
case 'blockquote':
|
||||
return <blockquote key={i}>{serialize(node.children)}</blockquote>
|
||||
case 'ul':
|
||||
return <ul key={i}>{serialize(node.children)}</ul>
|
||||
case 'ol':
|
||||
return <ol key={i}>{serialize(node.children)}</ol>
|
||||
case 'li':
|
||||
return <li key={i}>{serialize(node.children)}</li>
|
||||
case 'link':
|
||||
return (
|
||||
<a href={escapeHTML(node.url)} key={i}>
|
||||
{serialize(node.children)}
|
||||
</a>
|
||||
)
|
||||
|
||||
default:
|
||||
return <p key={i}>{serialize(node.children)}</p>
|
||||
}
|
||||
})
|
||||
|
||||
export default serialize
|
||||
117
examples/auth/next-app/app/_css/app.scss
Normal file
117
examples/auth/next-app/app/_css/app.scss
Normal file
@@ -0,0 +1,117 @@
|
||||
@use './queries.scss' as *;
|
||||
@use './colors.scss' as *;
|
||||
@use './type.scss' as *;
|
||||
@import "./theme.scss";
|
||||
|
||||
:root {
|
||||
--base: 24px;
|
||||
--font-body: system-ui;
|
||||
--font-mono: 'Roboto Mono', monospace;
|
||||
|
||||
--gutter-h: 180px;
|
||||
--block-padding: 120px;
|
||||
|
||||
@include large-break {
|
||||
--gutter-h: 144px;
|
||||
--block-padding: 96px;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
--gutter-h: 24px;
|
||||
--block-padding: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
@extend %body;
|
||||
background: var(--theme-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
margin: 0;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--theme-success-500);
|
||||
color: var(--color-base-800);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--theme-success-500);
|
||||
color: var(--color-base-800);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %h1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %h2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend %h3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@extend %h4;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@extend %h5;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@extend %h6;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: var(--base) 0;
|
||||
|
||||
@include mid-break {
|
||||
margin: calc(var(--base) * .75) 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: var(--base);
|
||||
margin: 0 0 var(--base);
|
||||
}
|
||||
|
||||
a {
|
||||
color: currentColor;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
83
examples/auth/next-app/app/_css/colors.scss
Normal file
83
examples/auth/next-app/app/_css/colors.scss
Normal file
@@ -0,0 +1,83 @@
|
||||
:root {
|
||||
--color-base-0: rgb(255, 255, 255);
|
||||
--color-base-50: rgb(245, 245, 245);
|
||||
--color-base-100: rgb(235, 235, 235);
|
||||
--color-base-150: rgb(221, 221, 221);
|
||||
--color-base-200: rgb(208, 208, 208);
|
||||
--color-base-250: rgb(195, 195, 195);
|
||||
--color-base-300: rgb(181, 181, 181);
|
||||
--color-base-350: rgb(168, 168, 168);
|
||||
--color-base-400: rgb(154, 154, 154);
|
||||
--color-base-450: rgb(141, 141, 141);
|
||||
--color-base-500: rgb(128, 128, 128);
|
||||
--color-base-550: rgb(114, 114, 114);
|
||||
--color-base-600: rgb(101, 101, 101);
|
||||
--color-base-650: rgb(87, 87, 87);
|
||||
--color-base-700: rgb(74, 74, 74);
|
||||
--color-base-750: rgb(60, 60, 60);
|
||||
--color-base-800: rgb(47, 47, 47);
|
||||
--color-base-850: rgb(34, 34, 34);
|
||||
--color-base-900: rgb(20, 20, 20);
|
||||
--color-base-950: rgb(7, 7, 7);
|
||||
--color-base-1000: rgb(0, 0, 0);
|
||||
|
||||
--color-success-50: rgb(247, 255, 251);
|
||||
--color-success-100: rgb(240, 255, 247);
|
||||
--color-success-150: rgb(232, 255, 243);
|
||||
--color-success-200: rgb(224, 255, 239);
|
||||
--color-success-250: rgb(217, 255, 235);
|
||||
--color-success-300: rgb(209, 255, 230);
|
||||
--color-success-350: rgb(201, 255, 226);
|
||||
--color-success-400: rgb(193, 255, 222);
|
||||
--color-success-450: rgb(186, 255, 218);
|
||||
--color-success-500: rgb(178, 255, 214);
|
||||
--color-success-550: rgb(160, 230, 193);
|
||||
--color-success-600: rgb(142, 204, 171);
|
||||
--color-success-650: rgb(125, 179, 150);
|
||||
--color-success-700: rgb(107, 153, 128);
|
||||
--color-success-750: rgb(89, 128, 107);
|
||||
--color-success-800: rgb(71, 102, 86);
|
||||
--color-success-850: rgb(53, 77, 64);
|
||||
--color-success-900: rgb(36, 51, 43);
|
||||
--color-success-950: rgb(18, 25, 21);
|
||||
|
||||
--color-warning-50: rgb(255, 255, 246);
|
||||
--color-warning-100: rgb(255, 255, 237);
|
||||
--color-warning-150: rgb(254, 255, 228);
|
||||
--color-warning-200: rgb(254, 255, 219);
|
||||
--color-warning-250: rgb(254, 255, 210);
|
||||
--color-warning-300: rgb(254, 255, 200);
|
||||
--color-warning-350: rgb(254, 255, 191);
|
||||
--color-warning-400: rgb(253, 255, 182);
|
||||
--color-warning-450: rgb(253, 255, 173);
|
||||
--color-warning-500: rgb(253, 255, 164);
|
||||
--color-warning-550: rgb(228, 230, 148);
|
||||
--color-warning-600: rgb(202, 204, 131);
|
||||
--color-warning-650: rgb(177, 179, 115);
|
||||
--color-warning-700: rgb(152, 153, 98);
|
||||
--color-warning-750: rgb(127, 128, 82);
|
||||
--color-warning-800: rgb(101, 102, 66);
|
||||
--color-warning-850: rgb(76, 77, 49);
|
||||
--color-warning-900: rgb(51, 51, 33);
|
||||
--color-warning-950: rgb(25, 25, 16);
|
||||
|
||||
--color-error-50: rgb(255, 241, 241);
|
||||
--color-error-100: rgb(255, 226, 228);
|
||||
--color-error-150: rgb(255, 212, 214);
|
||||
--color-error-200: rgb(255, 197, 200);
|
||||
--color-error-250: rgb(255, 183, 187);
|
||||
--color-error-300: rgb(255, 169, 173);
|
||||
--color-error-350: rgb(255, 154, 159);
|
||||
--color-error-400: rgb(255, 140, 145);
|
||||
--color-error-450: rgb(255, 125, 132);
|
||||
--color-error-500: rgb(255, 111, 118);
|
||||
--color-error-550: rgb(230, 100, 106);
|
||||
--color-error-600: rgb(204, 89, 94);
|
||||
--color-error-650: rgb(179, 78, 83);
|
||||
--color-error-700: rgb(153, 67, 71);
|
||||
--color-error-750: rgb(128, 56, 59);
|
||||
--color-error-800: rgb(102, 44, 47);
|
||||
--color-error-850: rgb(77, 33, 35);
|
||||
--color-error-900: rgb(51, 22, 24);
|
||||
--color-error-950: rgb(25, 11, 12);
|
||||
}
|
||||
1
examples/auth/next-app/app/_css/common.scss
Normal file
1
examples/auth/next-app/app/_css/common.scss
Normal file
@@ -0,0 +1 @@
|
||||
@forward './queries.scss';
|
||||
28
examples/auth/next-app/app/_css/queries.scss
Normal file
28
examples/auth/next-app/app/_css/queries.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
$breakpoint-xs-width: 400px;
|
||||
$breakpoint-s-width: 768px;
|
||||
$breakpoint-m-width: 1024px;
|
||||
$breakpoint-l-width: 1440px;
|
||||
|
||||
@mixin extra-small-break {
|
||||
@media (max-width: #{$breakpoint-xs-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin small-break {
|
||||
@media (max-width: #{$breakpoint-s-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mid-break {
|
||||
@media (max-width: #{$breakpoint-m-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large-break {
|
||||
@media (max-width: #{$breakpoint-l-width}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
241
examples/auth/next-app/app/_css/theme.scss
Normal file
241
examples/auth/next-app/app/_css/theme.scss
Normal file
@@ -0,0 +1,241 @@
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--theme-success-50: var(--color-success-50);
|
||||
--theme-success-100: var(--color-success-100);
|
||||
--theme-success-150: var(--color-success-150);
|
||||
--theme-success-200: var(--color-success-200);
|
||||
--theme-success-250: var(--color-success-250);
|
||||
--theme-success-300: var(--color-success-300);
|
||||
--theme-success-350: var(--color-success-350);
|
||||
--theme-success-400: var(--color-success-400);
|
||||
--theme-success-450: var(--color-success-450);
|
||||
--theme-success-500: var(--color-success-500);
|
||||
--theme-success-550: var(--color-success-550);
|
||||
--theme-success-600: var(--color-success-600);
|
||||
--theme-success-650: var(--color-success-650);
|
||||
--theme-success-700: var(--color-success-700);
|
||||
--theme-success-750: var(--color-success-750);
|
||||
--theme-success-800: var(--color-success-800);
|
||||
--theme-success-850: var(--color-success-850);
|
||||
--theme-success-900: var(--color-success-900);
|
||||
--theme-success-950: var(--color-success-950);
|
||||
|
||||
--theme-warning-50: var(--color-warning-50);
|
||||
--theme-warning-100: var(--color-warning-100);
|
||||
--theme-warning-150: var(--color-warning-150);
|
||||
--theme-warning-200: var(--color-warning-200);
|
||||
--theme-warning-250: var(--color-warning-250);
|
||||
--theme-warning-300: var(--color-warning-300);
|
||||
--theme-warning-350: var(--color-warning-350);
|
||||
--theme-warning-400: var(--color-warning-400);
|
||||
--theme-warning-450: var(--color-warning-450);
|
||||
--theme-warning-500: var(--color-warning-500);
|
||||
--theme-warning-550: var(--color-warning-550);
|
||||
--theme-warning-600: var(--color-warning-600);
|
||||
--theme-warning-650: var(--color-warning-650);
|
||||
--theme-warning-700: var(--color-warning-700);
|
||||
--theme-warning-750: var(--color-warning-750);
|
||||
--theme-warning-800: var(--color-warning-800);
|
||||
--theme-warning-850: var(--color-warning-850);
|
||||
--theme-warning-900: var(--color-warning-900);
|
||||
--theme-warning-950: var(--color-warning-950);
|
||||
|
||||
--theme-error-50: var(--color-error-50);
|
||||
--theme-error-100: var(--color-error-100);
|
||||
--theme-error-150: var(--color-error-150);
|
||||
--theme-error-200: var(--color-error-200);
|
||||
--theme-error-250: var(--color-error-250);
|
||||
--theme-error-300: var(--color-error-300);
|
||||
--theme-error-350: var(--color-error-350);
|
||||
--theme-error-400: var(--color-error-400);
|
||||
--theme-error-450: var(--color-error-450);
|
||||
--theme-error-500: var(--color-error-500);
|
||||
--theme-error-550: var(--color-error-550);
|
||||
--theme-error-600: var(--color-error-600);
|
||||
--theme-error-650: var(--color-error-650);
|
||||
--theme-error-700: var(--color-error-700);
|
||||
--theme-error-750: var(--color-error-750);
|
||||
--theme-error-800: var(--color-error-800);
|
||||
--theme-error-850: var(--color-error-850);
|
||||
--theme-error-900: var(--color-error-900);
|
||||
--theme-error-950: var(--color-error-950);
|
||||
|
||||
--theme-elevation-0: var(--color-base-0);
|
||||
--theme-elevation-50: var(--color-base-50);
|
||||
--theme-elevation-100: var(--color-base-100);
|
||||
--theme-elevation-150: var(--color-base-150);
|
||||
--theme-elevation-200: var(--color-base-200);
|
||||
--theme-elevation-250: var(--color-base-250);
|
||||
--theme-elevation-300: var(--color-base-300);
|
||||
--theme-elevation-350: var(--color-base-350);
|
||||
--theme-elevation-400: var(--color-base-400);
|
||||
--theme-elevation-450: var(--color-base-450);
|
||||
--theme-elevation-500: var(--color-base-500);
|
||||
--theme-elevation-550: var(--color-base-550);
|
||||
--theme-elevation-600: var(--color-base-600);
|
||||
--theme-elevation-650: var(--color-base-650);
|
||||
--theme-elevation-700: var(--color-base-700);
|
||||
--theme-elevation-750: var(--color-base-750);
|
||||
--theme-elevation-800: var(--color-base-800);
|
||||
--theme-elevation-850: var(--color-base-850);
|
||||
--theme-elevation-900: var(--color-base-900);
|
||||
--theme-elevation-950: var(--color-base-950);
|
||||
--theme-elevation-1000: var(--color-base-1000);
|
||||
|
||||
--theme-bg: var(--theme-elevation-0);
|
||||
--theme-input-bg: var(--theme-elevation-50);
|
||||
--theme-text: var(--theme-elevation-750);
|
||||
--theme-border-color: var(--theme-elevation-150);
|
||||
|
||||
color-scheme: light;
|
||||
color: var(--theme-text);
|
||||
|
||||
--highlight-default-bg-color: var(--theme-success-400);
|
||||
--highlight-default-text-color: var(--theme-text);
|
||||
|
||||
--highlight-danger-bg-color: var(--theme-error-150);
|
||||
--highlight-danger-text-color: var(--theme-text);
|
||||
}
|
||||
|
||||
h1 a,
|
||||
h2 a,
|
||||
h3 a,
|
||||
h4 a,
|
||||
h5 a,
|
||||
h6 a {
|
||||
color: var(--theme-elevation-750);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-elevation-800);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--theme-elevation-750);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-elevation-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--theme-elevation-0: var(--color-base-1000);
|
||||
--theme-elevation-50: var(--color-base-950);
|
||||
--theme-elevation-100: var(--color-base-900);
|
||||
--theme-elevation-150: var(--color-base-850);
|
||||
--theme-elevation-200: var(--color-base-800);
|
||||
--theme-elevation-250: var(--color-base-750);
|
||||
--theme-elevation-300: var(--color-base-700);
|
||||
--theme-elevation-350: var(--color-base-650);
|
||||
--theme-elevation-400: var(--color-base-600);
|
||||
--theme-elevation-450: var(--color-base-550);
|
||||
--theme-elevation-500: var(--color-base-500);
|
||||
--theme-elevation-550: var(--color-base-450);
|
||||
--theme-elevation-600: var(--color-base-400);
|
||||
--theme-elevation-650: var(--color-base-350);
|
||||
--theme-elevation-700: var(--color-base-300);
|
||||
--theme-elevation-750: var(--color-base-250);
|
||||
--theme-elevation-800: var(--color-base-200);
|
||||
--theme-elevation-850: var(--color-base-150);
|
||||
--theme-elevation-900: var(--color-base-100);
|
||||
--theme-elevation-950: var(--color-base-50);
|
||||
--theme-elevation-1000: var(--color-base-0);
|
||||
|
||||
--theme-success-50: var(--color-success-950);
|
||||
--theme-success-100: var(--color-success-900);
|
||||
--theme-success-150: var(--color-success-850);
|
||||
--theme-success-200: var(--color-success-800);
|
||||
--theme-success-250: var(--color-success-750);
|
||||
--theme-success-300: var(--color-success-700);
|
||||
--theme-success-350: var(--color-success-650);
|
||||
--theme-success-400: var(--color-success-600);
|
||||
--theme-success-450: var(--color-success-550);
|
||||
--theme-success-500: var(--color-success-500);
|
||||
--theme-success-550: var(--color-success-450);
|
||||
--theme-success-600: var(--color-success-400);
|
||||
--theme-success-650: var(--color-success-350);
|
||||
--theme-success-700: var(--color-success-300);
|
||||
--theme-success-750: var(--color-success-250);
|
||||
--theme-success-800: var(--color-success-200);
|
||||
--theme-success-850: var(--color-success-150);
|
||||
--theme-success-900: var(--color-success-100);
|
||||
--theme-success-950: var(--color-success-50);
|
||||
|
||||
--theme-warning-50: var(--color-warning-950);
|
||||
--theme-warning-100: var(--color-warning-900);
|
||||
--theme-warning-150: var(--color-warning-850);
|
||||
--theme-warning-200: var(--color-warning-800);
|
||||
--theme-warning-250: var(--color-warning-750);
|
||||
--theme-warning-300: var(--color-warning-700);
|
||||
--theme-warning-350: var(--color-warning-650);
|
||||
--theme-warning-400: var(--color-warning-600);
|
||||
--theme-warning-450: var(--color-warning-550);
|
||||
--theme-warning-500: var(--color-warning-500);
|
||||
--theme-warning-550: var(--color-warning-450);
|
||||
--theme-warning-600: var(--color-warning-400);
|
||||
--theme-warning-650: var(--color-warning-350);
|
||||
--theme-warning-700: var(--color-warning-300);
|
||||
--theme-warning-750: var(--color-warning-250);
|
||||
--theme-warning-800: var(--color-warning-200);
|
||||
--theme-warning-850: var(--color-warning-150);
|
||||
--theme-warning-900: var(--color-warning-100);
|
||||
--theme-warning-950: var(--color-warning-50);
|
||||
|
||||
--theme-error-50: var(--color-error-950);
|
||||
--theme-error-100: var(--color-error-900);
|
||||
--theme-error-150: var(--color-error-850);
|
||||
--theme-error-200: var(--color-error-800);
|
||||
--theme-error-250: var(--color-error-750);
|
||||
--theme-error-300: var(--color-error-700);
|
||||
--theme-error-350: var(--color-error-650);
|
||||
--theme-error-400: var(--color-error-600);
|
||||
--theme-error-450: var(--color-error-550);
|
||||
--theme-error-500: var(--color-error-500);
|
||||
--theme-error-550: var(--color-error-450);
|
||||
--theme-error-600: var(--color-error-400);
|
||||
--theme-error-650: var(--color-error-350);
|
||||
--theme-error-700: var(--color-error-300);
|
||||
--theme-error-750: var(--color-error-250);
|
||||
--theme-error-800: var(--color-error-200);
|
||||
--theme-error-850: var(--color-error-150);
|
||||
--theme-error-900: var(--color-error-100);
|
||||
--theme-error-950: var(--color-error-50);
|
||||
|
||||
--theme-bg: var(--theme-elevation-100);
|
||||
--theme-text: var(--theme-elevation-900);
|
||||
--theme-input-bg: var(--theme-elevation-150);
|
||||
--theme-border-color: var(--theme-elevation-250);
|
||||
|
||||
color-scheme: dark;
|
||||
color: var(--theme-text);
|
||||
|
||||
--highlight-default-bg-color: var(--theme-success-100);
|
||||
--highlight-default-text-color: var(--theme-success-600);
|
||||
|
||||
--highlight-danger-bg-color: var(--theme-error-100);
|
||||
--highlight-danger-text-color: var(--theme-error-550);
|
||||
}
|
||||
|
||||
h1 a,
|
||||
h2 a,
|
||||
h3 a,
|
||||
h4 a,
|
||||
h5 a,
|
||||
h6 a {
|
||||
color: var(--theme-success-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-success-400);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--theme-success-700);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-success-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
examples/auth/next-app/app/_css/type.scss
Normal file
110
examples/auth/next-app/app/_css/type.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
@use 'queries' as *;
|
||||
|
||||
%h1,
|
||||
%h2,
|
||||
%h3,
|
||||
%h4,
|
||||
%h5,
|
||||
%h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
%h1 {
|
||||
margin: 40px 0;
|
||||
font-size: 64px;
|
||||
line-height: 70px;
|
||||
font-weight: bold;
|
||||
|
||||
@include mid-break {
|
||||
margin: 24px 0;
|
||||
font-size: 42px;
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
%h2 {
|
||||
margin: 28px 0;
|
||||
font-size: 48px;
|
||||
line-height: 54px;
|
||||
font-weight: bold;
|
||||
|
||||
@include mid-break {
|
||||
margin: 22px 0;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
%h3 {
|
||||
margin: 24px 0;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
font-weight: bold;
|
||||
|
||||
@include mid-break {
|
||||
margin: 20px 0;
|
||||
font-size: 26px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
%h4 {
|
||||
margin: 20px 0;
|
||||
font-size: 26px;
|
||||
line-height: 32px;
|
||||
font-weight: bold;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
%h5 {
|
||||
margin: 20px 0;
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
font-weight: bold;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%h6 {
|
||||
margin: 20px 0;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
%body {
|
||||
font-size: 18px;
|
||||
line-height: 32px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
%large-body {
|
||||
font-size: 25px;
|
||||
line-height: 32px;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
%label {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
text-transform: uppercase;
|
||||
|
||||
@include mid-break {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
34
examples/auth/next-app/app/_providers/Auth/gql.ts
Normal file
34
examples/auth/next-app/app/_providers/Auth/gql.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const USER = `
|
||||
id
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
`
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const gql = async (query: string): Promise<any> => {
|
||||
try {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/graphql`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
}),
|
||||
})
|
||||
|
||||
const { data, errors } = await res.json()
|
||||
|
||||
if (errors) {
|
||||
throw new Error(errors[0].message)
|
||||
}
|
||||
|
||||
if (res.ok && data) {
|
||||
return data
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
177
examples/auth/next-app/app/_providers/Auth/index.tsx
Normal file
177
examples/auth/next-app/app/_providers/Auth/index.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
'use client'
|
||||
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { User } from '../../payload-types'
|
||||
import { gql, USER } from './gql'
|
||||
import { rest } from './rest'
|
||||
import { AuthContext, Create, ForgotPassword, Login, Logout, ResetPassword } from './types'
|
||||
|
||||
const Context = createContext({} as AuthContext)
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' | 'gql' }> = ({
|
||||
children,
|
||||
api = 'rest',
|
||||
}) => {
|
||||
const [user, setUser] = useState<User | null>()
|
||||
|
||||
const create = useCallback<Create>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
const { createUser: user } = await gql(`mutation {
|
||||
createUser(data: { email: "${args.email}", password: "${args.password}", firstName: "${args.firstName}", lastName: "${args.lastName}" }) {
|
||||
${USER}
|
||||
}
|
||||
}`)
|
||||
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
},
|
||||
[api],
|
||||
)
|
||||
|
||||
const login = useCallback<Login>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/login`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
const { loginUser } = await gql(`mutation {
|
||||
loginUser(email: "${args.email}", password: "${args.password}") {
|
||||
user {
|
||||
${USER}
|
||||
}
|
||||
exp
|
||||
}
|
||||
}`)
|
||||
|
||||
setUser(loginUser?.user)
|
||||
return loginUser?.user
|
||||
}
|
||||
},
|
||||
[api],
|
||||
)
|
||||
|
||||
const logout = useCallback<Logout>(async () => {
|
||||
if (api === 'rest') {
|
||||
await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/logout`)
|
||||
setUser(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
await gql(`mutation {
|
||||
logoutUser
|
||||
}`)
|
||||
|
||||
setUser(null)
|
||||
}
|
||||
}, [api])
|
||||
|
||||
// On mount, get user and set
|
||||
useEffect(() => {
|
||||
const fetchMe = async () => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`,
|
||||
{},
|
||||
{
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
setUser(user)
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
const { meUser } = await gql(`query {
|
||||
meUser {
|
||||
user {
|
||||
${USER}
|
||||
}
|
||||
exp
|
||||
}
|
||||
}`)
|
||||
|
||||
setUser(meUser.user)
|
||||
}
|
||||
}
|
||||
|
||||
fetchMe()
|
||||
}, [api])
|
||||
|
||||
const forgotPassword = useCallback<ForgotPassword>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(
|
||||
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`,
|
||||
args,
|
||||
)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
const { forgotPasswordUser } = await gql(`mutation {
|
||||
forgotPasswordUser(email: "${args.email}")
|
||||
}`)
|
||||
|
||||
return forgotPasswordUser
|
||||
}
|
||||
},
|
||||
[api],
|
||||
)
|
||||
|
||||
const resetPassword = useCallback<ResetPassword>(
|
||||
async args => {
|
||||
if (api === 'rest') {
|
||||
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, args)
|
||||
setUser(user)
|
||||
return user
|
||||
}
|
||||
|
||||
if (api === 'gql') {
|
||||
const { resetPasswordUser } = await gql(`mutation {
|
||||
resetPasswordUser(password: "${args.password}", token: "${args.token}") {
|
||||
user {
|
||||
${USER}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
setUser(resetPasswordUser.user)
|
||||
return resetPasswordUser.user
|
||||
}
|
||||
},
|
||||
[api],
|
||||
)
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
user,
|
||||
setUser,
|
||||
login,
|
||||
logout,
|
||||
create,
|
||||
resetPassword,
|
||||
forgotPassword,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
type UseAuth<T = User> = () => AuthContext // eslint-disable-line no-unused-vars
|
||||
|
||||
export const useAuth: UseAuth = () => useContext(Context)
|
||||
34
examples/auth/next-app/app/_providers/Auth/rest.ts
Normal file
34
examples/auth/next-app/app/_providers/Auth/rest.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { User } from '../../payload-types'
|
||||
|
||||
export const rest = async (
|
||||
url: string,
|
||||
args?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
options?: RequestInit,
|
||||
): Promise<User | null | undefined> => {
|
||||
const method = options?.method || 'POST'
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
...(method === 'POST' ? { body: JSON.stringify(args) } : {}),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
...options,
|
||||
})
|
||||
|
||||
const { errors, user } = await res.json()
|
||||
|
||||
if (errors) {
|
||||
throw new Error(errors[0].message)
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return user
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
throw new Error(e as string)
|
||||
}
|
||||
}
|
||||
41
examples/auth/next-app/app/_utilities/getMeUser.ts
Normal file
41
examples/auth/next-app/app/_utilities/getMeUser.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { cookies } from 'next/headers'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
import type { User } from '../payload-types'
|
||||
|
||||
export const getMeUser = async (args?: {
|
||||
nullUserRedirect?: string
|
||||
validUserRedirect?: string
|
||||
}): Promise<{
|
||||
user: User
|
||||
token: string | undefined
|
||||
}> => {
|
||||
const { nullUserRedirect, validUserRedirect } = args || {}
|
||||
const cookieStore = cookies()
|
||||
const token = cookieStore.get('payload-token')?.value
|
||||
|
||||
const meUserReq = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`, {
|
||||
headers: {
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
user,
|
||||
}: {
|
||||
user: User
|
||||
} = await meUserReq.json()
|
||||
|
||||
if (validUserRedirect && meUserReq.ok && user) {
|
||||
redirect(validUserRedirect)
|
||||
}
|
||||
|
||||
if (nullUserRedirect && (!meUserReq.ok || !user)) {
|
||||
redirect(nullUserRedirect)
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
token,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@import "../../_css/common";
|
||||
|
||||
.form {
|
||||
margin-bottom: var(--base);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--base) / 2);
|
||||
align-items: flex-start;
|
||||
width: 66.66%;
|
||||
|
||||
@include mid-break {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.changePassword {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: calc(var(--base) / 2);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user