Compare commits

..

521 Commits

Author SHA1 Message Date
Alessio Gravili
aa8d422421 fix tsconfig rscs paths 2024-11-11 10:37:21 -07:00
James
707775d1ae Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-11 12:34:36 -05:00
James
35ca534149 perf: speeds up admin ui by not fetching for initial user if logged in 2024-11-11 12:34:08 -05:00
Jacob Fletcher
12332ab5df removes custom server fn test 2024-11-11 12:33:33 -05:00
Jarrod Flesch
174631340d chore: duplicate id key error 2024-11-11 12:15:10 -05:00
Jacob Fletcher
dc8b1fe023 removes old custom server function reduce 2024-11-11 12:12:51 -05:00
Alessio Gravili
a24b72c530 exclude "TypeError: Failed to fetch" error from console error catch that is caused by server actions being aborted 2024-11-11 10:09:28 -07:00
James
3f74896167 chore: fixes form state for disabled fields, createFirstUser 2024-11-11 11:55:54 -05:00
Alessio Gravili
b240291913 make lexical tests wait even longer 2024-11-11 09:31:27 -07:00
Alessio Gravili
a2499352a5 rscs => rsc 2024-11-11 09:27:43 -07:00
Alessio Gravili
774838c421 ignore monaco load errors due to slow CI network 2024-11-11 09:14:50 -07:00
Jacob Fletcher
6d8fd3adeb removes custom server fn feature and related docs 2024-11-11 11:07:24 -05:00
James
7d627db8aa chore: fixes templates build 2024-11-11 11:06:11 -05:00
Jarrod Flesch
bda91d875f chore: adds autosave helper to live preview e2e 2024-11-11 10:56:10 -05:00
James
9eae3d685c chore: live preview e2e fix 2024-11-11 10:48:15 -05:00
Alessio Gravili
eb82ee457c Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-11 08:45:41 -07:00
Alessio Gravili
cc0b88a078 lexical tests: ensure blocks are rendered 2024-11-11 08:43:08 -07:00
Jarrod Flesch
faeef5bc93 chore: improve reliablity of autosave expectation 2024-11-11 09:56:49 -05:00
Jarrod Flesch
b841f01da5 chore: set defaults for version max docs on globals and collections 2024-11-11 09:15:43 -05:00
Alessio Gravili
2e816614ac fix broken upload file restoration in fields and admin test suites. No more console errors about payload.jpg not being found 2024-11-10 21:39:32 -07:00
Jarrod Flesch
0c4ec43460 fix: upload test order 2024-11-10 23:27:56 -05:00
Alessio Gravili
50bab5797c fix admin-root test suite 2024-11-10 20:42:38 -07:00
Alessio Gravili
de9acedaf4 maybe fix admin-root test suite 2024-11-10 20:33:50 -07:00
Alessio Gravili
6d6370ca5e fix auth and access-control test suites 2024-11-10 20:27:05 -07:00
Alessio Gravili
a3360b5d45 fix script for running all e2e tests 2024-11-10 20:08:03 -07:00
Alessio Gravili
e6f73e0c0d make extra sure that the memorydb is started, even if runInit retries 2024-11-10 20:03:11 -07:00
Alessio Gravili
f336b2a41e fix last int test 2024-11-10 19:58:52 -07:00
Alessio Gravili
7ed2859657 add ability to disable lint-staged via DISABLE_HUSKY environment variaböe 2024-11-10 19:56:05 -07:00
Alessio Gravili
d3c5cf7aff for reliability, always ensure indexes between every test case run 2024-11-10 19:12:54 -07:00
Alessio Gravili
bc3c949d95 fix incorrect importMap import for admin-root test suite 2024-11-10 18:51:56 -07:00
Alessio Gravili
ec7028ad5b fix jest not logging anything until entire test suite has finished 2024-11-10 18:49:27 -07:00
Alessio Gravili
6d2a5ddafe remove lie in console.log 2024-11-10 18:30:42 -07:00
Alessio Gravili
b83a7f2f37 make running e2e tests more reliable 2024-11-10 18:25:22 -07:00
Alessio Gravili
8deab489df fix incorrect importMap import for admin-root test suite 2024-11-10 14:03:04 -07:00
Alessio Gravili
eab9acb35d ensure mongo memory server is started when running e2e tests in CI 2024-11-10 13:51:54 -07:00
Alessio Gravili
150a31b339 passing int tests 2024-11-10 00:38:43 -07:00
Alessio Gravili
6c3e27288e log env variables 2024-11-10 00:35:09 -07:00
Alessio Gravili
3f2566a191 ensure matching react versions 2024-11-10 00:32:58 -07:00
Alessio Gravili
8bb994fc6c fix: ensure runE2e command passed down env variables and prod flag to run dev command 2024-11-10 00:22:13 -07:00
Alessio Gravili
3a48251219 fix running dev for nested test suite paths 2024-11-10 00:19:13 -07:00
Alessio Gravili
694040c8bc debug why failing tests show as passing 2024-11-10 00:08:52 -07:00
Jacob Fletcher
d5c9628958 Merge branch 'beta' into feat/on-demand-rsc 2024-11-08 17:31:26 -05:00
Jacob Fletcher
df6578183a properly initializes new doc drawer state for join field and renames renderDocument callback 2024-11-08 17:27:03 -05:00
Jarrod Flesch
a93da8625b chore: passing uploads 2024-11-08 17:21:11 -05:00
Jarrod Flesch
859726688e chore: fixes tests using previous selectors 2024-11-08 17:20:44 -05:00
Jacob Fletcher
7fb5b60932 fix join field query when creating new doc 2024-11-08 16:56:18 -05:00
Jacob Fletcher
46ebecfde4 updates underlying table after creating new from join field 2024-11-08 16:16:43 -05:00
Jacob Fletcher
6ea473aae8 gets initial columns within renderTable 2024-11-08 16:16:43 -05:00
Alessio Gravili
e0f85b5f05 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 13:30:20 -07:00
Alessio Gravili
6b2c939afd update packages 2024-11-08 13:20:34 -07:00
Alessio Gravili
20b09a8213 update lockfile 2024-11-08 13:18:24 -07:00
Alessio Gravili
7c44609af4 revert dumb code I wrote 2024-11-08 13:13:12 -07:00
Alessio Gravili
b0f243632d fix: ensure richext rscs import from client bundle 2024-11-08 13:07:41 -07:00
Jacob Fletcher
4df00f4b36 fix join field filter options 2024-11-08 14:59:02 -05:00
Alessio Gravili
7aa4a5650c fix(ui): ensure rscs not exported from client bundle imported from client bundle, in order to not duplicate components (& thus contexts) in the final bundle 2024-11-08 12:58:31 -07:00
Jacob Fletcher
1cae6cf997 returns resolved promise from getClientConfig 2024-11-08 14:33:26 -05:00
Jacob Fletcher
8a8055a0a8 proper fix for field paths 2024-11-08 14:26:20 -05:00
Jacob Fletcher
d0da6bc4e1 properly sets paths on nested unnamed fields 2024-11-08 13:56:32 -05:00
Alessio Gravili
d9711aaa89 fix e2e configs 2024-11-08 11:22:51 -07:00
Alessio Gravili
e26c95d430 fix wrong import 2024-11-08 11:09:03 -07:00
Alessio Gravili
fbb8229c54 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 10:38:34 -07:00
Jacob Fletcher
36925f3956 fix selectors in list column e2e tests 2024-11-08 12:37:40 -05:00
Alessio Gravili
50cc91ead3 chore: udpate all generated types 2024-11-08 10:37:12 -07:00
Alessio Gravili
58c901dfdd fix another autosave test 2024-11-08 10:36:25 -07:00
Alessio Gravili
f599735d9d fix another autosave test 2024-11-08 10:35:05 -07:00
Alessio Gravili
c9752918c8 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-08 09:34:31 -07:00
Alessio Gravili
0b5a294f63 fix incorrectly set up monorepo app folders 2024-11-08 09:33:38 -07:00
Alessio Gravili
7422fab2f2 fix plugin-stripe type error 2024-11-08 09:19:53 -07:00
Alessio Gravili
3fd74dd77f export UI field component types 2024-11-08 09:19:37 -07:00
Alessio Gravili
0392b3d614 fix lexical errors 2024-11-08 09:07:54 -07:00
Jacob Fletcher
f1c9b2b1d0 fix next build errors 2024-11-08 10:58:04 -05:00
Alessio Gravili
997b9eb63d fix type issues, allow '' paths to take precedence over field names in field components 2024-11-08 08:47:02 -07:00
Jacob Fletcher
f8ab503153 deflakes list filtering e2e tests 2024-11-08 10:19:58 -05:00
Alessio Gravili
3e105a20e4 get slate and lexical to build 2024-11-08 08:02:15 -07:00
Jacob Fletcher
906551bba4 fix where builder 2024-11-08 09:56:19 -05:00
Jacob Fletcher
6167bf3cca bulk edit 2024-11-08 09:39:35 -05:00
Jarrod Flesch
5ec26e8d54 chore: fixes ui, conditional, indexed field test suites 2024-11-08 09:14:09 -05:00
Alessio Gravili
d2bf12ac56 fix(ui): ensure autosave is not triggered endlessly 2024-11-07 23:42:34 -07:00
Alessio Gravili
a33131033f fix(ui): frontend version count going above maximum version count 2024-11-07 23:41:59 -07:00
Alessio Gravili
b13eac201a switch to isRedirectError 2024-11-07 17:42:31 -07:00
Jacob Fletcher
65553af461 adjusts initialData logic 2024-11-07 19:37:26 -05:00
Alessio Gravili
b212e2d989 fix(next): new doc creation for autosave-enabled collections. We need to allow NEXT_REDIRECT errors to throw instead of catching them 2024-11-07 17:31:52 -07:00
Alessio Gravili
3ee58f2211 fix(next): /create edit view not rendering any fields 2024-11-07 17:14:32 -07:00
Alessio Gravili
b2dabb78bb Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 15:57:10 -07:00
Jacob Fletcher
b78b1af0af properly increments edit depth for rich text drawers and more 2024-11-07 17:26:31 -05:00
Jacob Fletcher
42e20bd982 resolves type in buildTableState error handler 2024-11-07 16:46:21 -05:00
Jacob Fletcher
b8d18a73da resolves more types 2024-11-07 16:39:22 -05:00
Alessio Gravili
76ae85566e fix: lexical blocks kicking focus out of editor when writing 2024-11-07 14:38:32 -07:00
Jacob Fletcher
0f081a4999 fixes default file cell field type 2024-11-07 16:19:24 -05:00
Jacob Fletcher
36ce0438eb prevents infinitely referencing props 2024-11-07 16:19:24 -05:00
Jarrod Flesch
60fbc3e251 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 16:01:13 -05:00
Jarrod Flesch
6d11f790c0 chore: fix collapsible field test 2024-11-07 16:01:00 -05:00
James
2add224ee5 chore: makes alessio happy 2024-11-07 15:57:27 -05:00
James
8f3b0fd71b Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 15:55:10 -05:00
James
6a78be6b17 chore: fixes types 2024-11-07 15:54:56 -05:00
Alessio Gravili
f8bc243144 make fieldSchemaMap of fieldSchemasToFormState optional and add JSDocs 2024-11-07 13:30:54 -07:00
Alessio Gravili
6a6aa9a85f fix(richtext-lexical): fix missing case where empty lexical editors with required: true were not detected as empty 2024-11-07 13:30:24 -07:00
Alessio Gravili
fac30ad95e fix(richtext-lexical): correct usages of fieldSchemasToFormState for validation 2024-11-07 13:29:36 -07:00
James
58c792cfc9 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 14:56:32 -05:00
James
2536107abb chore: fixes types 2024-11-07 14:55:52 -05:00
Alessio Gravili
057a37b50e fix docPermissions and getFormState type errors 2024-11-07 12:12:25 -07:00
Jacob Fletcher
aa402bca12 fix create new relationship drawer 2024-11-07 13:28:07 -05:00
Jacob Fletcher
64154e0818 fix edit depth, differentiates from drawer depth 2024-11-07 13:28:07 -05:00
Alessio Gravili
81a904af96 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 10:42:39 -07:00
Alessio Gravili
0d468ffc6c fix(richtext-lexical): cell: replace headless editor functions to get text content with good old recursive loop, and improve display of non-text node types.
Using the headless editor was very glitchy. If the server encounters any error anywhere, then any consecutive visit of the list view, where the headless editor will run, will throw a lexical error about no active editor being found when "parseEditorState" is run, despite parseEditorState initializing a new active editor. This is cached in module scope, and server-side errors in Next.js seem to break that behavior for some reason
2024-11-07 10:41:23 -07:00
Jarrod Flesch
c35b3164a4 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 12:22:53 -05:00
Jarrod Flesch
f4517685df adjust list header rendering 2024-11-07 12:22:22 -05:00
Alessio Gravili
51ec6b2fa4 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-11-07 10:17:40 -07:00
Alessio Gravili
fdbf86d394 fix document drawer abort controller madness 2024-11-07 09:52:38 -07:00
Jacob Fletcher
b9c41ee095 fix existing doc drawer initialization 2024-11-07 11:37:14 -05:00
Alessio Gravili
652e6910c9 fix some lexical tests again 2024-11-07 09:11:52 -07:00
James
a749617a04 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 11:07:38 -05:00
James
f3cf304b6a chore: type fixes 2024-11-07 11:07:19 -05:00
Alessio Gravili
9d5dd5a61a fix flakes 2024-11-07 08:40:23 -07:00
Alessio Gravili
aa5abeea7e fix: re-render entire field when removing a row. Otherwise, the array index in paths of RSC sibling fields will not change 2024-11-07 08:40:09 -07:00
James
e11ad3371b Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-07 10:05:17 -05:00
James
d352c35c22 chore: type fixes 2024-11-07 10:04:35 -05:00
Jarrod Flesch
ac85bc4d04 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 09:56:28 -05:00
Jarrod Flesch
4b2e86328a fix: upload allowCreate 2024-11-07 09:56:16 -05:00
Jarrod Flesch
5e3eab7db4 chore: revert list header classname to current beta css class name 2024-11-07 09:55:51 -05:00
Jacob Fletcher
cde1135106 fix relationship drawers 2024-11-07 09:38:31 -05:00
Jarrod Flesch
fcecad6b2f Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-07 09:16:53 -05:00
Jarrod Flesch
1e696bdbde chore: passing fields->relationships 2024-11-07 09:16:40 -05:00
Alessio Gravili
0c8debe71d fix some slate tests due to new class names 2024-11-06 22:19:07 -07:00
Alessio Gravili
2bba6c80c5 fix: ensure drawers increase editdepth 2024-11-06 22:18:13 -07:00
Alessio Gravili
301666006c reduce lexical flakes 2024-11-06 21:14:32 -07:00
Alessio Gravili
205650d26d reduce lexical flakes 2024-11-06 21:12:09 -07:00
Jarrod Flesch
05b83f71de Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 21:10:34 -05:00
Jarrod Flesch
f097287c8c chore: passing fields->blocks 2024-11-06 21:08:47 -05:00
Jarrod Flesch
1caf5d9327 chore: passing fields->email 2024-11-06 21:07:43 -05:00
James
23afac9b01 chore: passing form-builder tests 2024-11-06 17:52:36 -05:00
James
4548d30f03 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-06 17:47:17 -05:00
James
b7a1f4c773 chore: passing plugin-seo 2024-11-06 17:47:03 -05:00
Jacob Fletcher
6cc18e9a21 renames renderFieldMethod to renderFieldFn 2024-11-06 16:05:01 -05:00
Jacob Fletcher
0a61160665 deflakes custom logout route e2e 2024-11-06 15:35:10 -05:00
Jarrod Flesch
3c288facda Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 15:12:49 -05:00
Jarrod Flesch
f9ea396955 chore: passing fields->array 2024-11-06 15:12:32 -05:00
Alessio Gravili
9cccbba2e1 increase expect timeout. 2.5s is sometimes not enough for saving document 2024-11-06 13:09:28 -07:00
Jacob Fletcher
29c9e45ce6 deflakes routing e2e 2024-11-06 15:05:19 -05:00
Jarrod Flesch
f70ef8dac3 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-06 14:59:29 -05:00
Jarrod Flesch
d6d214b745 implement RowLabels 2024-11-06 14:59:14 -05:00
Alessio Gravili
90d9d6e17d speed up reinitDB for mongodb by not waiting for index creation to be completed 2024-11-06 12:13:25 -07:00
Alessio Gravili
f2d2630336 log time for reInitializeDB 2024-11-06 11:57:53 -07:00
James
62724b54df Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-06 13:32:56 -05:00
Alessio Gravili
096eefd005 do not let playwright handle starting up the dev server, as playwrights babel black magic was messing up our code. A dev server can now be re-used for multiple, separate test runs 2024-11-06 11:31:47 -07:00
James
4d75c56ef1 perf: loads document dependencies in parallel to reduce load time 2024-11-06 13:25:05 -05:00
Alessio Gravili
bd56af14d0 simplify lexical cell editor state handling 2024-11-06 11:12:23 -07:00
Jarrod Flesch
df007d42ea chore: fixes field labels, brings back array test config 2024-11-06 13:04:34 -05:00
Jacob Fletcher
dbaff20b1b fix flaky document meta tests 2024-11-06 13:03:29 -05:00
Jacob Fletcher
d400a7e77d prevents id field from rendering and dedupes duplicative custom id 2024-11-06 12:05:42 -05:00
Jarrod Flesch
791699146e chore: remove select from getVersions findVersions call 2024-11-06 12:01:21 -05:00
Jarrod Flesch
760200c1be chore: passing auth test suite 2024-11-06 10:47:28 -05:00
Jacob Fletcher
94f8787270 creates renderListViewSlots fn and avoids sending unnecessary slots, properly handles list view descriptions 2024-11-06 10:23:05 -05:00
Alessio Gravili
29a6ec1ee2 minor fixes 2024-11-06 01:51:06 -07:00
Alessio Gravili
9030aa81c3 fix(richtext-lexical): fix cannot find active editor state errors when setting editor state of headless Editor.
This happened through Slate => Upload Node Component => Swap (opens list drawer) => Select lexical error => Error here. This error is caused by a new initialization of an UploadNode
2024-11-06 01:39:46 -07:00
Alessio Gravili
6dce541b33 slate: ensure initial or required form state requests are not aborted 2024-11-06 01:30:38 -07:00
Alessio Gravili
55a9e946cc slate: fully-functional rsc-only cell component 2024-11-06 01:26:14 -07:00
Alessio Gravili
33dbcb5115 ensure lexical cell component displays "noLabel" if it's empty, instead of just an empty cell 2024-11-06 01:14:36 -07:00
Alessio Gravili
2dff78ae61 pass i18n to cell component serverProps 2024-11-06 01:13:46 -07:00
Alessio Gravili
189e514ecf fully-functional, lexical cell component. This is now an rsc, not a client component 2024-11-06 01:04:43 -07:00
Alessio Gravili
2e18638baf pass payload to server cell components, render richtext cells 2024-11-06 01:03:36 -07:00
Alessio Gravili
73c9a34918 passing lexical tests, and less flakes than on current beta 2024-11-05 23:14:09 -07:00
Jacob Fletcher
223cf6348e removes _isPreviewEnabled property from client config 2024-11-05 22:50:14 -05:00
Jacob Fletcher
69bcfbf2c6 properly retrieves in document controls and cleans up document slots 2024-11-05 22:43:41 -05:00
Jacob Fletcher
4e002310e3 fix props in custom field description within admin tests 2024-11-05 22:12:18 -05:00
Jacob Fletcher
81948094c3 fix document tabs key prop 2024-11-05 21:59:17 -05:00
Jacob Fletcher
1d7dc6926f fix document tabs key prop 2024-11-05 21:59:17 -05:00
Alessio Gravili
6c8de6665e reduce flakes, reduce test timeout when running locally 2024-11-05 15:57:41 -07:00
Alessio Gravili
3014f96113 fix(richtext-lexical): add missing dependency array 2024-11-05 15:50:41 -07:00
Alessio Gravili
b97c143ef8 fix(richtext-lexical): drawer fields sometimes not appearing. This ensures that all initial state requests can never be aborted 2024-11-05 15:50:19 -07:00
Jacob Fletcher
0642127094 reintroduces getDocPermissions into document info provider 2024-11-05 17:15:45 -05:00
Jarrod Flesch
4d85b91afe clear step nav on dashboard view 2024-11-05 16:56:32 -05:00
Jarrod Flesch
498d849905 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 16:49:18 -05:00
Jarrod Flesch
796c15dffc chore: renders versions list view 2024-11-05 16:49:04 -05:00
Jarrod Flesch
20723e2d79 adjust custom save button styles 2024-11-05 16:21:14 -05:00
Jacob Fletcher
fbc82d488a moves doc drawer header definition out from view 2024-11-05 16:14:10 -05:00
Jarrod Flesch
20e11e3fc3 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 16:12:42 -05:00
Jarrod Flesch
4c29ab0c44 chore: working global dependency components 2024-11-05 16:12:25 -05:00
Jacob Fletcher
dc572a1433 properly throws not-found for unauthorized docs 2024-11-05 15:46:48 -05:00
Jacob Fletcher
da1dd11949 properly throws not found based on doc permissions 2024-11-05 15:03:16 -05:00
Jarrod Flesch
8a9cf9bbdf Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 14:35:33 -05:00
Jarrod Flesch
d0f31e7dc4 chore: renders custom list view Description with props 2024-11-05 14:35:13 -05:00
Alessio Gravili
87ccd740ab lexical: more reliable slash menu closing, avoid nested editor.update's 2024-11-05 12:32:05 -07:00
Jarrod Flesch
dc6515a26e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 14:12:24 -05:00
Jarrod Flesch
ea0fc62f5e attach custom list components to list view 2024-11-05 14:12:11 -05:00
Alessio Gravili
d8a4a6f92d perf(richtext-lexical): optimize slash menu item selection by minimizing editor.update calls and setTimeouts. This gets rid of occasional errors when selecting a slash menu entry 2024-11-05 12:12:07 -07:00
Jacob Fletcher
8fa047d3b6 account view 2024-11-05 13:41:59 -05:00
Jarrod Flesch
27c51c5114 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 13:31:31 -05:00
Jarrod Flesch
6dff40dc69 improve custom list Description props 2024-11-05 13:31:18 -05:00
Alessio Gravili
31c2a2a42a fix: ensure server form state is not merged if it's null 2024-11-05 11:30:56 -07:00
Jarrod Flesch
18d9a21bac realign list subheader to match current beta dom 2024-11-05 13:24:49 -05:00
Jarrod Flesch
c918991172 chore: use types in custom cell component 2024-11-05 13:16:33 -05:00
Jarrod Flesch
9bb2d97e75 chore: fix types remove _isPresentational prop 2024-11-05 13:12:43 -05:00
Jacob Fletcher
aad8e9385c removes logs 2024-11-05 13:08:16 -05:00
Jacob Fletcher
1807aac5c2 safely checks admin.disabled in form state 2024-11-05 13:07:09 -05:00
Jacob Fletcher
e50a899d92 fix custom server field in _community 2024-11-05 13:00:58 -05:00
Jacob Fletcher
d6d3101632 fixes step nav 2024-11-05 12:55:27 -05:00
Jarrod Flesch
516975b5cc chore: exports missing imports for ui package 2024-11-05 12:34:17 -05:00
Jarrod Flesch
164020ea24 fix handleServerFunctions export/import 2024-11-05 11:59:52 -05:00
Jacob Fletcher
0e328372ec Merge branch 'beta' into feat/on-demand-rsc 2024-11-05 11:59:47 -05:00
Jarrod Flesch
da6154afb4 fix missing handleServerFunctions export 2024-11-05 11:51:33 -05:00
Jarrod Flesch
181d8a686b Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 11:27:58 -05:00
Jarrod Flesch
5d91a2e273 chore: adjusts field not rendering logic 2024-11-05 11:27:33 -05:00
Alessio Gravili
fbe4295497 add back commented out fields test suite stuff 2024-11-05 09:27:07 -07:00
Alessio Gravili
ca485bdc0e fix fields not rendering 2024-11-05 09:23:52 -07:00
Jarrod Flesch
aa5d1f52d8 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 11:22:39 -05:00
Jarrod Flesch
4534b74e05 adds providers back in 2024-11-05 11:22:25 -05:00
Jacob Fletcher
d3c78deb08 safely accesses admin components 2024-11-05 11:12:45 -05:00
Jacob Fletcher
a96982597b enables relationship table sort 2024-11-05 11:08:51 -05:00
Jacob Fletcher
6952b751e9 poc linked cell overrides for relationship table 2024-11-05 11:08:25 -05:00
Jarrod Flesch
52ca879722 reuse Logo component 2024-11-05 11:03:23 -05:00
Jarrod Flesch
10570936bd feat: implements custom logout, avatar, Icon and Logo 2024-11-05 10:19:49 -05:00
Jarrod Flesch
f5e749f62f completes readOnly prop to custom components 2024-11-05 08:25:08 -05:00
Jarrod Flesch
15f485e10b Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-05 00:23:12 -05:00
Alessio Gravili
83712998c5 restore whole fields test suite 2024-11-04 20:13:43 -07:00
Alessio Gravili
0f1397815b working richtext-slate 2024-11-04 19:47:25 -07:00
Alessio Gravili
7e215f570e prevent error if array field does not receive permissions 2024-11-04 17:56:11 -07:00
Alessio Gravili
48523e1703 chore: fix jarrod 2024-11-04 17:49:55 -07:00
Alessio Gravili
560d5adf97 fix type errors 2024-11-04 17:46:11 -07:00
Alessio Gravili
a19a7235f1 misc fixes 2024-11-04 17:39:04 -07:00
Jarrod Flesch
8fef77075c chore: swap useField out for useFormFields 2024-11-04 17:08:43 -05:00
Jacob Fletcher
4d5f06de86 fixes search 2024-11-04 17:06:24 -05:00
Jarrod Flesch
6e39d25604 chore: remove useForm from renderFields 2024-11-04 16:58:43 -05:00
Jarrod Flesch
9c11be879e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 15:01:56 -05:00
Jarrod Flesch
503b94c7dc type cleanup inside RenderFields 2024-11-04 15:01:15 -05:00
Jacob Fletcher
af0d3564b2 wip join field 2024-11-04 14:12:47 -05:00
Alessio Gravili
3607e4bd94 fix: ensure tabs save data by making sure the path & schemaPath of children are correct 2024-11-04 12:11:51 -07:00
Alessio Gravili
a504b08965 add missing prop to hook array 2024-11-04 11:38:30 -07:00
Alessio Gravili
65d895d7f6 fix: ensure sidebar fields do not cause error in RenderFields 2024-11-04 11:38:03 -07:00
Alessio Gravili
54509033c6 fix: tab index paths being incorrectly prepended to named field's paths 2024-11-04 11:26:17 -07:00
Alessio Gravili
eba967df78 almost restore fields test suite to its full glory 2024-11-04 11:02:14 -07:00
Alessio Gravili
968269aea9 fix: non-serializable formstate was being sent to onSubmit and beforeSubmit form hooks, leading to server action errors as client components were passed to the server action 2024-11-04 10:51:44 -07:00
Jarrod Flesch
6dd09679a5 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 12:49:28 -05:00
Jarrod Flesch
67acbfde18 chore: move permissions to serverProps and only require on client for fields with subfields 2024-11-04 12:49:12 -05:00
Alessio Gravili
f70db1b818 fix(richtext-lexical): fix node.getPoint errors when working with drawers 2024-11-04 10:40:58 -07:00
Alessio Gravili
da0093610c lexical: get remaining features to work 2024-11-04 10:32:01 -07:00
Jarrod Flesch
f50f5d24f5 chore: delete blacklisted client-to-server keys instead of setting undefined 2024-11-04 11:34:31 -05:00
Jarrod Flesch
10ba80264e Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-11-04 11:29:20 -05:00
Jarrod Flesch
d3990d1108 fix: schema paths should not include _index in schema paths unless they are leaf fields 2024-11-04 11:29:09 -05:00
Alessio Gravili
4c146776ab remove console.log 2024-11-04 08:52:16 -07:00
Alessio Gravili
cbafa720bd lexical: working blocks, working fields drawer 2024-11-04 08:51:44 -07:00
Jacob Fletcher
e81b3a8b35 fix list view filter options 2024-11-04 09:55:08 -05:00
Jacob Fletcher
3df7f6996e relocates document and list view server fn handlers 2024-11-04 09:36:05 -05:00
Jacob Fletcher
349686fce8 renames renderListFn 2024-11-04 09:26:13 -05:00
Jarrod Flesch
2066184187 fix: prevent drawer from closing upon create 2024-11-04 08:43:22 -05:00
Alessio Gravili
0647d63dd9 get lexical blocks working again, fix incorrect usages of getFormState 2024-11-04 06:38:05 -07:00
Jarrod Flesch
de1591fcb5 fix: incorrect status after publishing 2024-11-04 08:21:14 -05:00
Alessio Gravili
4a11a8e723 get lexical to load up again, type improvements 2024-11-04 00:01:59 -07:00
Alessio Gravili
e24ac29e80 no need to add duplicative fieldState to serverProps 2024-11-04 00:01:22 -07:00
Alessio Gravili
665dba9fea get field hooks to work again after getFieldPaths changes 2024-11-04 00:01:01 -07:00
Jacob Fletcher
2ea70f324a renames ListInfoProvider to ListDrawerContextProvider for parity 2024-11-02 12:44:09 -04:00
James
32df51c78e chore: unsets requiresRender from server 2024-11-02 11:59:16 -04:00
James
b05f888625 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-11-02 11:33:46 -04:00
James
0b55bb4456 chore: threads through requiresRender for arrays / blocks 2024-11-02 11:33:26 -04:00
Jarrod Flesch
58321e396b fix: bad auto import 2024-11-01 22:22:38 -04:00
Jarrod Flesch
c07ce89f40 fix reduceToSerializableFields usage 2024-11-01 22:19:55 -04:00
Jarrod Flesch
1d3453c1a4 chore: implement reduceToSerializableFields function for form fields 2024-11-01 22:18:29 -04:00
Jarrod Flesch
7e3e4dbb0a removes prepareFields from Form 2024-11-01 22:09:04 -04:00
Jarrod Flesch
829a2d3c3c chore: fix versions fetching 2024-11-01 22:00:25 -04:00
Jarrod Flesch
8a9aead49e fix old path array 2024-11-01 21:54:31 -04:00
Jacob Fletcher
b52fa96b4b partially reenables fields test seed 2024-11-01 16:53:44 -04:00
Alessio Gravili
116a03b162 custom array comp 2024-11-01 14:44:29 -06:00
James
f3698db5b5 chore: cleanup 2024-11-01 16:37:17 -04:00
James
ceb49e70a3 chore: fixes typez 2024-11-01 16:08:02 -04:00
James
ebfcd4a53f chore: functional pattern 2024-11-01 16:05:25 -04:00
James
6c2925b3b4 chore: progress to field paths 2024-11-01 13:09:12 -04:00
James
1ab9f2cc75 Merge branch 'feat/on-demand-rsc' of github.com:payloadcms/payload into feat/on-demand-rsc 2024-10-31 14:44:08 -04:00
James
ac0dc57ac4 feat: adds countVersions, heavily refactors DocumentInfo, improves ssr workload 2024-10-31 14:43:47 -04:00
Alessio Gravili
3f0c810569 migrate more stuff off of componentMap 2024-10-31 11:08:54 -06:00
Alessio Gravili
19b44e34b9 make more robust 2024-10-31 10:58:00 -06:00
Alessio Gravili
41ceaf5ac0 add back lexical collections 2024-10-31 10:30:39 -06:00
Alessio Gravili
ad9d18408c fix blocks 2024-10-31 10:27:45 -06:00
Alessio Gravili
1da2118d6d get blocks to render 2024-10-31 09:46:55 -06:00
Jacob Fletcher
6ad6085894 passes enableRowSelections through buildTableState 2024-10-30 18:02:17 -04:00
Jacob Fletcher
2f295f3df1 poc list drawer filter/search/sort 2024-10-30 17:16:08 -04:00
Jarrod Flesch
5baa2c4964 fix relationship drawer multiple fetches on open 2024-10-30 16:57:14 -04:00
Jarrod Flesch
e5506a39aa Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 16:04:59 -04:00
Jarrod Flesch
6857cc08e8 fix mismatch client and server form states on blocks 2024-10-30 16:04:44 -04:00
Jacob Fletcher
a53763ca2c fix: safely accesses view actions 2024-10-30 14:03:21 -04:00
Alessio Gravili
f883c69e11 fix: jacob's negligence 2024-10-30 11:46:20 -06:00
Jarrod Flesch
6a680773ca Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 13:00:55 -04:00
Jarrod Flesch
02503dbd45 corrects permission passing to renderFields 2024-10-30 13:00:42 -04:00
Jacob Fletcher
b7943b9f8e Merge branch 'beta' into feat/on-demand-rsc 2024-10-30 12:22:23 -04:00
Jarrod Flesch
184325b2cf fix hidden field props and id 2024-10-30 11:36:04 -04:00
Jacob Fletcher
72370a1581 create drawer create and duplicate 2024-10-30 11:09:39 -04:00
Jacob Fletcher
f80cc2089a cleanup 2024-10-30 11:09:39 -04:00
Jacob Fletcher
4c1fd28ce5 consolidates listinfocontext 2024-10-30 11:09:39 -04:00
Jacob Fletcher
b7b3ad16b7 poc polymorphic list drawers 2024-10-30 11:09:39 -04:00
Alessio Gravili
74bc281f3d lexical: get rid of generateClientProps in favor of field rsc 2024-10-30 09:07:27 -06:00
Jarrod Flesch
c21b555368 account for Files in deepCopyObjectComplex 2024-10-30 11:03:24 -04:00
Jarrod Flesch
e53877a868 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 11:00:34 -04:00
Jarrod Flesch
8ef1180fc1 fix view actions for root global 2024-10-30 11:00:23 -04:00
Alessio Gravili
52a006abd9 fix: RenderServerComponent does not respect PayloadComponent.clientProps/serverProps 2024-10-30 08:54:12 -06:00
Jarrod Flesch
6206b46b86 cant keep looking at ugly header 2024-10-30 10:52:45 -04:00
Jarrod Flesch
e13c7057e4 fixes formState files inside attachComponentsToFormState 2024-10-30 10:44:53 -04:00
Jarrod Flesch
3d9dd0b647 fix viewActions array build up 2024-10-30 09:28:48 -04:00
Jarrod Flesch
4b4ef7b4c1 sanitize serverFunctions for client 2024-10-30 08:54:20 -04:00
Jarrod Flesch
34dd3ac479 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-30 08:45:10 -04:00
Jarrod Flesch
f6f0aee553 chore: adjusts how actions are stored in the provider and set on views 2024-10-30 08:44:27 -04:00
Alessio Gravili
5d07d40960 fix: ensure newly added arrays have their children's custom components rendered. This is done by ensuring that requireRender is correctly set in the form state constructed by buildFormState, if there is an incoming previous formState 2024-10-29 22:44:29 -06:00
Alessio Gravili
98f5418ac0 fix: rendered custom components disappearing when submitting form, as prepareFields was messing up local form state 2024-10-29 22:22:24 -06:00
Alessio Gravili
b63b0cf3ad chore: fix typo 2024-10-29 22:16:35 -06:00
Alessio Gravili
2611c77211 feat: start getting lexical to work. Gets rid of generateComponentMap in favor of generateClientProps, simplifies a lot 2024-10-29 22:15:48 -06:00
Alessio Gravili
0b948673fa perf: only re-render necessary fields in form state requests, only trigger one form state request instead of two when adding new array row 2024-10-29 22:14:41 -06:00
Jarrod Flesch
f6c0dcacee chore: remove SetViewActions 2024-10-29 16:59:16 -04:00
Jarrod Flesch
4a639ccc19 fix action button creation 2024-10-29 16:55:53 -04:00
Jacob Fletcher
2c03de6dc7 conditionally renders create new btn in list view based on drawer 2024-10-29 16:48:21 -04:00
Jarrod Flesch
d7c57aeeb9 chore: gets viewActions on the server 2024-10-29 16:16:55 -04:00
Jacob Fletcher
e8d12bc3c0 fix custom cells and column labels 2024-10-29 16:02:12 -04:00
Jacob Fletcher
564591ed93 removes fields without labels from col selector 2024-10-29 13:33:16 -04:00
Jacob Fletcher
185764c477 fix filename cells 2024-10-29 13:22:13 -04:00
Jacob Fletcher
b11f072ad8 fix enable row selections logic 2024-10-29 11:40:44 -04:00
Jarrod Flesch
cb2771d3a9 default fieldState to empty object 2024-10-29 11:11:20 -04:00
Jarrod Flesch
3176e87a95 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-29 10:41:51 -04:00
Jarrod Flesch
76f3102d07 rm console log 2024-10-29 10:41:40 -04:00
Jacob Fletcher
53f05ad303 defers rendering fallback column labels to client 2024-10-29 10:32:30 -04:00
Jarrod Flesch
8e1ef2222d Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-29 10:32:13 -04:00
Jarrod Flesch
40d5ae3165 implements RenderCustomComponent 2024-10-29 10:31:45 -04:00
Jacob Fletcher
fe69f9ec35 fix doc drawer callbacks 2024-10-29 09:56:39 -04:00
Jacob Fletcher
a56c6a5757 poc doc drawer context 2024-10-28 18:01:56 -04:00
Jacob Fletcher
186f886c1d poc list drawer context 2024-10-28 18:01:43 -04:00
Jarrod Flesch
2e9af8d258 uncomment test suite text-fields config 2024-10-28 16:44:54 -04:00
Jarrod Flesch
7162ce9ef5 fix readonly not being threaded to array row render fields 2024-10-28 16:21:34 -04:00
Jarrod Flesch
b6b58550e1 correctly fallback on client for field labels, errors, descriptions 2024-10-28 16:11:49 -04:00
Jarrod Flesch
bfd08bbeaf Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 11:45:01 -04:00
Jarrod Flesch
290594a232 chore: migrate fields to extract custom components from fieldState 2024-10-28 11:44:44 -04:00
Alessio Gravili
c6325c13cf fix field component props: Array - Hidden 2024-10-28 08:59:28 -06:00
Jacob Fletcher
c7cd6e3fbb fix upload field allowCreate logic 2024-10-28 10:48:31 -04:00
Alessio Gravili
414e68e463 fix custom component rendering other than Field 2024-10-28 08:47:50 -06:00
Jarrod Flesch
a6df86d596 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 10:27:21 -04:00
Jarrod Flesch
78ffe523f5 fix form array mutations, fixes custom components disappearing again 2024-10-28 10:27:07 -04:00
Alessio Gravili
03645d172d fix: use consistent field component types 2024-10-28 08:18:49 -06:00
Alessio Gravili
9f7924b930 Merge remote-tracking branch 'origin/beta' into feat/on-demand-rsc 2024-10-27 23:56:02 -06:00
Jarrod Flesch
9f5f909094 fix ui fields disappearing, fix replaceFieldRow fn 2024-10-28 01:05:56 -04:00
Jarrod Flesch
997ddb4654 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-28 00:49:15 -04:00
Jarrod Flesch
73ee8b5549 fix: allow customComponents to be merged into state 2024-10-28 00:48:54 -04:00
Jacob Fletcher
8195bd804b poc list drawers 2024-10-28 00:10:41 -04:00
Jacob Fletcher
4395dc8901 auto increments edit depth 2024-10-26 17:57:36 -04:00
Jacob Fletcher
7668e0907e poc server renders document drawers 2024-10-26 17:56:38 -04:00
Jacob Fletcher
c46946a77c client-side renders default document buttons 2024-10-25 17:24:44 -04:00
Jacob Fletcher
4194b8bc61 rendering fields in drawers 2024-10-25 17:24:17 -04:00
Jarrod Flesch
807500d55f fix block and array fields 2024-10-25 16:53:01 -04:00
Jarrod Flesch
456eea1344 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-25 15:50:17 -04:00
Jarrod Flesch
af8fed9ab3 working blocks 2024-10-25 15:49:52 -04:00
Jacob Fletcher
38dc313051 sets column prefs server side 2024-10-25 14:18:48 -04:00
Jarrod Flesch
5a53c6b130 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-25 12:32:15 -04:00
Jarrod Flesch
d3dd8aef53 more working things 2024-10-25 12:32:03 -04:00
Jacob Fletcher
7b39acc54d fix custom filter lookup 2024-10-25 12:31:10 -04:00
Jarrod Flesch
e7b69ce70f thread readOnly into default fields 2024-10-25 10:50:22 -04:00
Jarrod Flesch
91b65b4cc6 Merge branch 'rsc-schemaPaths' into feat/on-demand-rsc 2024-10-25 10:08:25 -04:00
Jarrod Flesch
42f78480ba threads localized prop into input components 2024-10-25 10:07:17 -04:00
Jarrod Flesch
bbe0fa38ae Merge branch 'beta' into rsc-schemaPaths 2024-10-25 10:06:38 -04:00
Jarrod Flesch
d32798a2a0 removes unused props, threads readOnly through to missing fields 2024-10-25 09:20:56 -04:00
Jarrod Flesch
11c505930d undo test in array config 2024-10-25 00:52:58 -04:00
Jarrod Flesch
0dcb109101 Merge remote-tracking branch 'refs/remotes/origin/rsc-schemaPaths' into rsc-schemaPaths 2024-10-25 00:26:38 -04:00
Jarrod Flesch
95a2bc4d1e wires up field permissions 2024-10-25 00:18:25 -04:00
Alessio Gravili
3f9c7e2acd remove console log 2024-10-24 15:33:18 -06:00
Alessio Gravili
50a1770d7e fix buildFormState for partial schema paths (fixes adding array rows) 2024-10-24 15:32:37 -06:00
Alessio Gravili
9bf24c0379 fix schema path handling in createClientConfig 2024-10-24 15:13:54 -06:00
Alessio Gravili
2429f64f3a fix faulty logic in buildPathSegments 2024-10-24 15:09:24 -06:00
Alessio Gravili
5adcff3e76 fix usage of createClientField 2024-10-24 14:52:37 -06:00
Alessio Gravili
bc155c4a87 use array paths / schema paths where possible, add _index- path segments to server-side operation paths / schema paths, merge generatePath and getFieldPaths 2024-10-24 14:45:40 -06:00
Jacob Fletcher
cb03d5d197 renders filter component slots 2024-10-24 15:34:07 -04:00
Jarrod Flesch
b3021b559a fix row styling 2024-10-24 14:35:09 -04:00
Jarrod Flesch
1bc7d91c4f remove path path debug paragraph text 2024-10-24 14:27:57 -04:00
Jarrod Flesch
42dd173986 Merge remote-tracking branch 'refs/remotes/origin/rsc-schemaPaths' into rsc-schemaPaths 2024-10-24 14:26:02 -04:00
Jarrod Flesch
d9b188061d feat: working arrays 2024-10-24 14:25:02 -04:00
Alessio Gravili
8e5ec02037 ensure form state always includes _index, ensure _index is always stripped away from DATA 2024-10-24 10:19:19 -06:00
Jacob Fletcher
1d370e0d69 col ordering 2024-10-24 11:00:12 -04:00
Alessio Gravili
e51067ccaf fix: remove _index- from data before sending it to the server, as it's not processed by the server 2024-10-23 22:55:26 -06:00
Alessio Gravili
d48cb1b8eb fix schemaPath handling for collapsibles and rows 2024-10-23 22:22:51 -06:00
Alessio Gravili
acc4432a99 fix partial schema path handling 2024-10-23 22:06:58 -06:00
Alessio Gravili
be5772b71c fix buildStateFromSchema path input 2024-10-23 21:59:46 -06:00
Alessio Gravili
c1222b5e06 fixes 2024-10-23 21:49:10 -06:00
Jarrod Flesch
24d5bc88f6 dont render if hidden 2024-10-23 23:24:12 -04:00
Alessio Gravili
06750e1f4a remove schemaAccessor 2024-10-23 21:20:20 -06:00
Alessio Gravili
0aaa4fe643 throw proper error if block data contains unknown block slug 2024-10-23 20:54:34 -06:00
Alessio Gravili
cb6a0249fb fix array error 2024-10-23 20:54:16 -06:00
Jacob Fletcher
3383bf3479 poc table sorting 2024-10-23 17:56:16 -04:00
Jarrod Flesch
06536eb275 chore: almost working 2024-10-23 17:07:34 -04:00
Jacob Fletcher
e881dcd4b4 poc toggle columns 2024-10-23 16:10:20 -04:00
Jarrod Flesch
67acde45e6 not working 2024-10-23 14:08:55 -04:00
Jacob Fletcher
d6498e442f explores fields in form state 2024-10-22 17:56:27 -04:00
Jarrod Flesch
7e50fc51f1 Merge remote-tracking branch 'refs/remotes/origin/feat/on-demand-rsc' into feat/on-demand-rsc 2024-10-22 16:48:04 -04:00
Jarrod Flesch
b49f9f92be chore: rendering rows 2024-10-22 16:47:34 -04:00
Jacob Fletcher
9ffbb3f7f9 poc list view 2024-10-22 16:25:05 -04:00
Jacob Fletcher
54301c9088 renders cells in column state 2024-10-22 16:25:05 -04:00
Jacob Fletcher
809df54cf0 progress to ssr tables 2024-10-22 16:25:04 -04:00
Jacob Fletcher
d254a6e622 begins server rendering list cells 2024-10-22 16:25:04 -04:00
Jarrod Flesch
f92dcf68c6 chore: refactor, merge generatePath and generateFieldKey 2024-10-22 15:08:44 -04:00
Jarrod Flesch
c8cba855a2 chore: thread docPrefs through to getFormState 2024-10-22 12:39:22 -04:00
Jarrod Flesch
4e5121f6d3 chore: refactors function params 2024-10-22 12:08:12 -04:00
Jarrod Flesch
c3eefec31f remove renderedFieldMap prop 2024-10-21 17:08:10 -04:00
Jarrod Flesch
7dbd1d861f fixes inifite rendering of collapsibles, remove early return for un-named fields 2024-10-21 17:07:13 -04:00
Jacob Fletcher
3ff9e34199 fixes array rows 2024-10-21 15:38:26 -04:00
Jacob Fletcher
e7c1f98b50 fix nested index paths 2024-10-21 15:27:35 -04:00
Jacob Fletcher
7aa604c0d7 reworks field key 2024-10-21 13:52:13 -04:00
Jacob Fletcher
fbfa0fd5d6 wip 2024-10-21 12:36:36 -04:00
Jacob Fletcher
be149b362a working addFieldRow and begins migrating more fields 2024-10-21 11:19:07 -04:00
Jacob Fletcher
0e05c5d60d working field keys 2024-10-21 10:46:49 -04:00
Jacob Fletcher
91cd7672e3 progress to unique field keys 2024-10-19 14:11:29 -04:00
Jacob Fletcher
ca8e8becd2 begins migrating remaining fields 2024-10-18 10:55:10 -04:00
Jacob Fletcher
b09bd65020 group fields 2024-10-18 10:42:23 -04:00
Jacob Fletcher
7882c83f03 threads index path through renderFields 2024-10-18 00:37:51 -04:00
Jacob Fletcher
7498099ede fixes index path lookup 2024-10-17 23:46:48 -04:00
Jacob Fletcher
f800cb8dc5 back to traditional field schema map 2024-10-17 17:59:32 -04:00
Jacob Fletcher
f6360d055f begins setting fields test suite back to original state 2024-10-17 11:54:39 -04:00
Jacob Fletcher
a597579354 blocks field 2024-10-17 11:31:55 -04:00
Jacob Fletcher
7d2fc41f19 a little hacky but it works 2024-10-17 09:50:10 -04:00
Jacob Fletcher
01ba2c0114 working nested arrays 2024-10-16 14:22:45 -04:00
Jacob Fletcher
f0b431e799 ensures values are not lost when rendering new array items 2024-10-16 11:36:32 -04:00
Jacob Fletcher
b68b625899 array items are dynamically rendering again 2024-10-16 11:06:47 -04:00
Jacob Fletcher
57f8475780 reworks fieldSchemaMap to accomodate new pattern 2024-10-16 09:07:49 -04:00
Jacob Fletcher
e14a8876ab puts rows into state instead of fields 2024-10-14 13:46:43 -04:00
Jacob Fletcher
e9cd82bc81 poc array rows 2024-10-14 12:00:03 -04:00
Jacob Fletcher
054d183a96 progress 2024-10-13 22:20:41 -04:00
Jacob Fletcher
e03a330fd3 please be the last poc 2024-10-11 15:59:13 -04:00
Jacob Fletcher
9038020dd9 nested fields are rendering 2024-10-10 15:17:02 -04:00
Jacob Fletcher
1f0e551578 omg it works 2024-10-10 13:12:03 -04:00
Jacob Fletcher
f2c5750e78 poc rendering components in field state 2024-10-09 14:56:46 -04:00
Jacob Fletcher
23f136ab82 path is now a direct prop 2024-10-08 15:49:22 -04:00
Jacob Fletcher
aa38ac9bd8 cleanup 2024-10-08 14:14:11 -04:00
Jacob Fletcher
5d3294b341 fix nested arrays 2024-10-08 14:05:29 -04:00
Jacob Fletcher
7fddc5fbd9 poc arrays 2024-10-08 13:51:41 -04:00
Jacob Fletcher
d056b0b964 properly isolates handleServerFunctions export to avoid client-side contamination 2024-10-07 13:19:58 -04:00
Jacob Fletcher
57e9109f93 Merge branch 'beta' into feat/on-demand-rsc 2024-10-07 10:27:21 -04:00
Jacob Fletcher
515629b51d cleanup 2024-10-07 10:13:57 -04:00
Jacob Fletcher
6ec779fb6c Squashed commit of the following:
commit 7a0609ab57
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 10:00:34 2024 -0400

    wires abort controller into server fn provider

commit 633aaa721d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:50:12 2024 -0400

    adjust err handling

commit 860b6cf2bf
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:26:01 2024 -0400

    safely accesses signal

commit 463e05d7b9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:13:39 2024 -0400

    adds additional ac check prior to firing action

commit 231c4b3d1f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 09:00:45 2024 -0400

    cleanup

commit 494c970b0a
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Mon Oct 7 08:46:28 2024 -0400

    more acs

commit 8dcb2eae17
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 23:33:53 2024 -0400

    adds abort controllers to docinfo and form actions

commit 0da4326e43
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 23:05:14 2024 -0400

    adds back remaining actions

commit 292d8b53e4
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 22:53:44 2024 -0400

    thrads abort controller to onchange

commit ca8629e5a6
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sun Oct 6 22:02:20 2024 -0400

    reintroduces some actions

commit 5f001c0f52
Merge: 75dd030e2 2ba40f333
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sat Oct 5 09:15:25 2024 -0400

    Merge branch 'beta' into feat/server-actions

commit 2ba40f3335
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Sat Oct 5 09:13:43 2024 -0400

    chore: removes duplicative join field test (#8558)

    There are two of the exact same e2e tests for the join field, which
    throws an error when running these tests locally because they have
    identical names.

commit 463490f670
Author: Elliot DeNolf <denolfe@users.noreply.github.com>
Date:   Fri Oct 4 18:38:27 2024 -0700

    fix(templates): await params/cookies properly (#8560)

commit d564cd44e9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 17:29:38 2024 -0400

    chore: deflakes lexical e2e test (#8559)

    This has caused me great pain. The problem with this test is that the
    page was waiting for a URL which includes a search query that never
    arrives. This moves the check into a regex pattern for a more accurate
    catch.

commit 75dd030e24
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 16:26:19 2024 -0400

    reverts reset back to fetch to test

commit 7c62e2a327
Author: Paul <paul@payloadcms.com>
Date:   Fri Oct 4 13:02:56 2024 -0600

    feat(ui)!: scope all payload css to payload-default layer (#8545)

    All payload css is now encapsulated inside CSS layers under `@layer
    payload-default`

    Any custom css will now have the highest possible specificity.
    We have also provided a new layer `@layer payload` if you want to use
    layers and ensure that your styles are applied after payload.

    To override existing styles in a way that the existing rules of
    specificity would be respected you can use the default layer like so
    ```css
    @layer payload-default {
      // my styles within the payload specificity
    }
    ```

commit 400293b8ee
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 21:46:41 2024 +0300

    fix: duplicate with upload collections (#8552)

    Fixes the duplicate operation with uploads
    Enables duplicate for upload collections by default

commit e4a413eb9a
Author: Elliot DeNolf <denolfe@gmail.com>
Date:   Fri Oct 4 11:31:06 2024 -0700

    chore(release): v3.0.0-beta.111 [skip ci]

commit b99590f477
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 21:28:43 2024 +0300

    chore(templates): update templates with next.js promises (#8547)

    Updates templates according to this PR
    https://github.com/payloadcms/payload/pull/8489

commit a4704c1453
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 13:53:52 2024 -0400

    moves docinfo back to fetch to test

commit 0d3416c96d
Author: Alessio Gravili <alessio@gravili.de>
Date:   Fri Oct 4 13:39:03 2024 -0400

    fix(db-postgres): missing types for db.pool by moving @types/pg from devDependencies to dependencies (#8556)

    Fixes lack of types in installed project:

    ![CleanShot 2024-10-04 at 19 18
    58@2x](https://github.com/user-attachments/assets/e7c519ee-72fd-424b-8f6c-41032322fa5e)

    Since we expose stuff from @types/pg to the end user, we need it to be
    installed in the end users project => move to dependencies.

commit 2f6ee80a6a
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 12:54:45 2024 -0400

    changes addFieldRow back to fetch

commit 0128eedf70
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 19:25:05 2024 +0300

    fix(drizzle)!: make radio and select column names to snake_case (#8439)

    Fixes https://github.com/payloadcms/payload/issues/8402 and
    https://github.com/payloadcms/payload/issues/8027

    Before DB column names were camelCase:

    ![image](https://github.com/user-attachments/assets/d2965bcf-290a-4f86-9bf4-dfe7e8613934)

    After this change, they are snake_case:
    ![Screenshot 2024-10-04
    114226](https://github.com/user-attachments/assets/bbc8c20b-6745-4dd3-b0c8-56263a4e37b1)

    #### Breaking SQLite / Postgres ⚠️
    If you had any select (not `hasMany: true`) or radio fields with the
    name in camelCase, for example:
    ```ts
    {
      name: 'selectReadOnly',
      type: 'select',
      admin: {
        readOnly: true,
      },
      options: [
        {
          label: 'Value One',
          value: 'one',
        },
        {
          label: 'Value Two',
          value: 'two',
        },
      ],
    },
    ```
    This previously was mapped to the db column name `"selectReadOnly"`. Now
    it's `select_read_only`.
    Generate a new migration to rename your columns.
    ```sh
    pnpm payload migrate:create
    ```
    Then select "rename column" for targeted columns and Drizzle will handle
    the migration.

    ---------

    Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>

commit 414030e1f1
Author: Sasha <64744993+r1tsuu@users.noreply.github.com>
Date:   Fri Oct 4 18:48:54 2024 +0300

    fix(drizzle): row / collapsible inside of localized fields (#8539)

    Fixes https://github.com/payloadcms/payload/issues/8405

commit 41efcbe4ac
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 10:02:48 2024 -0400

    build err

commit 2203d6046c
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 08:32:13 2024 -0400

    reverts default edit view onChange

commit 2cd9594f55
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 07:47:49 2024 -0400

    brings back actions for everything except lexical

commit cc2cad3140
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:58:35 2024 -0400

    more cleanup

commit 87e11bd6ad
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:20:02 2024 -0400

    remove website lockfile

commit caf4c4a0df
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:12:25 2024 -0400

    properly scopes effect cleanup return

commit 3ddd972de0
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:07:09 2024 -0400

    adjusts abort controllers

commit 6c95f0a658
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Fri Oct 4 00:00:00 2024 -0400

    fix api route

commit 9206adedee
Merge: 726f8b003 f6eb027f2
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 23:45:17 2024 -0400

    Merge branch 'beta' into feat/server-actions

commit 726f8b003d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 23:11:53 2024 -0400

    again

commit 40e5571c0e
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 22:56:55 2024 -0400

    more

commit bdbe7dd41f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 22:10:48 2024 -0400

    temprarily brings back fetch to debug

commit 08bd461d4b
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 19:43:59 2024 -0400

    properly aborts on unmount

commit cf617767e5
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 19:21:42 2024 -0400

    prevents signal sent from client to server

commit ee853234fd
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 18:35:23 2024 -0400

    temp debug

commit 23cd537a33
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 14:59:35 2024 -0400

    removes auto gen upload dirs from test

commit 0a950c0f0b
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 14:47:56 2024 -0400

    plz

commit f84822e7d9
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:48:10 2024 -0400

    returns errors instead of throws

commit ee577f53f2
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:34:36 2024 -0400

    increases test bodysizelimit

commit 6641bff085
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 13:25:44 2024 -0400

    removes default server action body size limit

commit 53a6dcf213
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 12:49:04 2024 -0400

    plz be it

commit 9e8e7c7c3d
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 11:48:11 2024 -0400

    creates shared callback for common server fns

commit b4d081db3f
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Thu Oct 3 09:39:40 2024 -0400

    some docs

commit bc36bf7984
Author: Jacob Fletcher <jacobsfletch@gmail.com>
Date:   Wed Oct 2 16:26:25 2024 -0400

    increases default serverActions.bodySizeLimit
2024-10-07 10:11:44 -04:00
Jacob Fletcher
84fd8d06f0 Merge feat/server-actions into feat/on-demand-rsc 2024-10-07 09:44:15 -04:00
Jacob Fletcher
01b580cb24 cleanup 2024-10-04 12:37:04 -04:00
Jacob Fletcher
4d60f9c7da Merge branch 'beta' into feat/on-demand-rsc 2024-10-04 12:16:00 -04:00
Jacob Fletcher
fde05840fe begins wiring array action 2024-10-04 12:05:59 -04:00
Jacob Fletcher
8ed1766d5c Merge branch 'feat/server-actions' into feat/on-demand-rsc 2024-10-02 15:44:40 -04:00
Jacob Fletcher
6fa47bf854 fix missing id 2024-10-02 15:16:50 -04:00
Jacob Fletcher
362cd1712d fix error handling 2024-10-02 14:55:51 -04:00
Jacob Fletcher
e669368149 proper error handling and cleanup 2024-10-02 13:45:55 -04:00
Jacob Fletcher
068d7eec52 adds next-env.d.ts to tsconfig.include 2024-10-02 12:51:02 -04:00
Jacob Fletcher
fb861a53ec small docs 2024-10-02 12:50:48 -04:00
Jacob Fletcher
f16e55fff2 fixes docinfo server fn 2024-10-02 12:08:46 -04:00
Jacob Fletcher
47c19224c2 Merge branch 'beta' into feat/server-actions 2024-10-02 11:17:17 -04:00
Jacob Fletcher
fd2444e614 properly formats errors and fixes imports in templates 2024-10-02 11:16:12 -04:00
Jacob Fletcher
31ffe3bc43 adds back missing css imports 2024-10-02 10:11:54 -04:00
Jacob Fletcher
4cd89cd5d7 fixes type imports 2024-10-02 10:06:24 -04:00
Jacob Fletcher
1d7bcb365d removes duplicate test 2024-10-02 09:39:03 -04:00
Jacob Fletcher
8ffc090cac Merge branch 'beta' into feat/server-actions 2024-10-02 09:35:02 -04:00
Jacob Fletcher
039bd0f76d adds proper error handling 2024-10-02 09:33:54 -04:00
Jacob Fletcher
5235ce819d temp 2024-10-02 00:03:08 -04:00
Jacob Fletcher
c1d2736e8b reverts auto-changes to test modules 2024-10-01 23:41:50 -04:00
Jacob Fletcher
eca0a25063 regenerates lockfile 2024-10-01 23:33:12 -04:00
Jacob Fletcher
e737c8db32 fix test component deps 2024-10-01 23:27:10 -04:00
Jacob Fletcher
bc84def8d8 temp: filter args before calling server fn 2024-10-01 23:22:38 -04:00
Jacob Fletcher
2cf4a58e89 renames ClientServerFunction to ServerFunctionClient 2024-10-01 22:26:59 -04:00
Jacob Fletcher
9c8f623068 renames props to proper plural/singular for semantics 2024-10-01 22:05:16 -04:00
Jacob Fletcher
60edd35671 more docs 2024-10-01 17:52:34 -04:00
Jacob Fletcher
bca9aece06 cleanup 2024-10-01 17:16:51 -04:00
Jacob Fletcher
e43a03d0ff scaffolds docs 2024-10-01 17:13:07 -04:00
Jacob Fletcher
4424904f58 excludes examples until released 2024-10-01 16:48:50 -04:00
Jacob Fletcher
5150a5a30f lints 2024-10-01 16:44:32 -04:00
Jacob Fletcher
b6d829acd8 updates all app dirs 2024-10-01 16:33:29 -04:00
Jacob Fletcher
41d622f613 adds temp log to test ci 2024-10-01 16:11:18 -04:00
Jacob Fletcher
c32c7d50ef builds 2024-10-01 14:42:47 -04:00
Jacob Fletcher
d59c1c01c9 cleanup 2024-10-01 14:32:15 -04:00
Jacob Fletcher
e5ce24eafb Merge branch 'beta' into feat/server-actions 2024-10-01 14:07:04 -04:00
Jacob Fletcher
0de81afa92 aborts server function results in docinfoprovider 2024-10-01 14:06:31 -04:00
Jacob Fletcher
0f5fe98a1b renames BaseServerFunctionArgs to DefaultServerFunctionArgs 2024-10-01 12:32:00 -04:00
Jacob Fletcher
e330f1756f renames RootServerFunction type to ServerFunctionHandler 2024-10-01 12:30:33 -04:00
Jacob Fletcher
c353a0f296 renames serverFunction to plural 2024-10-01 12:27:39 -04:00
Jacob Fletcher
a7c1dd057d properly inits req 2024-10-01 12:02:30 -04:00
Jacob Fletcher
3cbf7b2603 gets language from action itself and properly drills user into local req 2024-10-01 11:27:30 -04:00
Jacob Fletcher
a4135e5975 renames function property to fn 2024-10-01 10:49:04 -04:00
Jacob Fletcher
7fb860f15b removes unnecessary useAuth hooks 2024-10-01 10:43:52 -04:00
Jacob Fletcher
e684f3ac2e adds test for custom server functions 2024-10-01 10:23:06 -04:00
Jacob Fletcher
ac25118945 sanitizes server functions from client config 2024-10-01 00:47:42 -04:00
Jacob Fletcher
9a859a453e cleanup 2024-09-30 23:25:38 -04:00
Jacob Fletcher
dc46f18af9 epxoses in config and renames server actions to server function in preparation for react v19-beta 2024-09-30 22:20:53 -04:00
Jacob Fletcher
271a8c7191 cleanup 2024-09-30 17:51:15 -04:00
Jacob Fletcher
0dbc3bad57 passing tests 2024-09-30 17:46:46 -04:00
Jacob Fletcher
4f0cb93204 creates local req 2024-09-30 15:54:57 -04:00
Jacob Fletcher
b035afe4e3 adds auth 2024-09-30 15:36:24 -04:00
Jacob Fletcher
d33f9f5a1c feat!: server actions 2024-09-30 10:06:33 -04:00
Jacob Fletcher
ccba668dc1 reworks array rows and renders tabs 2024-09-28 14:29:28 -04:00
Jacob Fletcher
9188fbe396 fields with subfields 2024-09-27 16:58:35 -04:00
Jacob Fletcher
4d66c65958 cleanup old references 2024-09-27 12:18:12 -04:00
Jacob Fletcher
66c767f201 deprecates old RenderFields 2024-09-27 12:05:46 -04:00
Jacob Fletcher
4daf22c03c deprecates field props context 2024-09-27 11:36:56 -04:00
Jacob Fletcher
30cc5a018c field labels 2024-09-27 10:54:04 -04:00
Jacob Fletcher
71eb66b393 poc array fields 2024-09-27 10:35:20 -04:00
Jacob Fletcher
b88fabf148 Merge branch 'beta' into feat/on-demand-rsc 2024-09-26 23:02:16 -04:00
Jacob Fletcher
25385a4923 begins removing MappedComponent 2024-09-26 18:15:40 -04:00
Jacob Fletcher
911d93c207 migrating remaining fields 2024-09-26 18:06:53 -04:00
Jacob Fletcher
afe19b3c53 handles hidden and disabled fields 2024-09-26 17:37:20 -04:00
Jacob Fletcher
95c3eb3313 more field slots 2024-09-26 16:15:33 -04:00
Jacob Fletcher
b5ea0a787d establishes pattern for field slots 2024-09-26 15:40:54 -04:00
Jacob Fletcher
8849655afc handles sidebar fields 2024-09-26 13:36:00 -04:00
Jacob Fletcher
868698ed47 establishes render entity pattern 2024-09-26 13:18:07 -04:00
Jacob Fletcher
7291adc3c2 threads field state through props 2024-09-26 12:24:12 -04:00
Jacob Fletcher
3c71e2880e renders fields server side 2024-09-26 10:53:46 -04:00
Jacob Fletcher
b840bea4cf caches client config 2024-09-26 10:48:34 -04:00
Jacob Fletcher
a5f82d8a16 bootable edit view 2024-09-25 16:31:25 -04:00
Jacob Fletcher
0d109be224 rendering list view 2024-09-25 13:40:55 -04:00
Jacob Fletcher
c36b6a43a4 rendering admin 2024-09-25 12:50:12 -04:00
Jacob Fletcher
a154a86350 properly exports render component and achives bootable state 2024-09-25 12:49:39 -04:00
Jacob Fletcher
e9815e6ec7 Merge branch 'beta' into feat/on-demand-rsc 2024-09-25 09:48:26 -04:00
Jacob Fletcher
cdde8d729d cleanup 2024-09-25 09:40:37 -04:00
Jacob Fletcher
487599e2ee rewrites RenderComponent, removing all getCreateMappedComponent instances 2024-09-24 23:38:45 -04:00
Jacob Fletcher
c7f3278d93 rewrites client config 2024-09-24 19:44:51 -04:00
Jacob Fletcher
4d8159e9aa Merge branch 'beta' into feat/on-demand-rsc 2024-09-24 13:58:21 -04:00
Jacob Fletcher
e5956051f2 renders lite configs at root and successfully builds 2024-09-24 11:47:24 -04:00
Jacob Fletcher
1155c0aa22 reworks list info provider 2024-09-23 18:07:36 -04:00
Jacob Fletcher
6ca2f1d28b fixes nav 2024-09-23 14:30:15 -04:00
Jacob Fletcher
5d3193a164 fixes add new 2024-09-23 13:45:48 -04:00
Jacob Fletcher
d0af4f2271 collection labels 2024-09-23 12:37:18 -04:00
Jacob Fletcher
69c74ecbbc fixes duplicative render 2024-09-23 12:07:19 -04:00
Jacob Fletcher
76cc178d36 threads field state to server field components 2024-09-23 10:40:44 -04:00
Jacob Fletcher
b63e18573e adjusts config loading hierarchy 2024-09-23 10:02:25 -04:00
Jacob Fletcher
b61d271bd5 successfully threads data through server components 2024-09-23 00:35:10 -04:00
Jacob Fletcher
ddc57dd5cf works 2024-09-22 23:39:24 -04:00
Jacob Fletcher
f53ef13f4b moves views to ui 2024-09-22 23:14:26 -04:00
Jacob Fletcher
168a8c5317 executes server actions client-side 2024-09-22 13:23:55 -04:00
Jacob Fletcher
5d496c60fa achieves rendering state 2024-09-21 18:24:35 -04:00
Jacob Fletcher
72c206551b poc server actions pattern 2024-09-20 23:26:23 -04:00
999 changed files with 30870 additions and 93002 deletions

View File

@@ -28,6 +28,3 @@ fb7d1be2f3325d076b7c967b1730afcef37922c2
# Prettier and lint remaining db packages
7fd736ea5b2e9fc4ef936e9dc9e5e3d722f6d8bf
# Bump all eslint deps, lint and format
03291472d6e427ff94e61fca0616cca7796a3a95

View File

@@ -39,7 +39,6 @@ body:
- 'db-postgres'
- 'db-sqlite'
- 'db-vercel-postgres'
- 'email-nodemailer'
- 'plugin: cloud'
- 'plugin: cloud-storage'
- 'plugin: form-builder'

View File

@@ -284,7 +284,7 @@ jobs:
tests-e2e:
runs-on: ubuntu-latest
needs: build
name: e2e-${{ matrix.suite }}
name: ${{ matrix.suite }}
strategy:
fail-fast: false
matrix:
@@ -294,7 +294,6 @@ jobs:
- access-control
- admin__e2e__1
- admin__e2e__2
- admin__e2e__3
- admin-root
- auth
- field-error-states
@@ -415,10 +414,6 @@ jobs:
- template: with-vercel-postgres
database: postgres
# Re-enable once PG conncection is figured out
# - template: with-vercel-website
# database: postgres
name: ${{ matrix.template }}-${{ matrix.database }}
env:
@@ -530,7 +525,6 @@ jobs:
publish-canary:
name: Publish Canary
runs-on: ubuntu-latest
if: ${{ needs.all-green.result == 'success' && github.ref_name == 'beta' }}
needs:
- all-green
@@ -539,3 +533,4 @@ jobs:
- run: |
echo github.ref: ${{ github.ref }}
echo isBeta: ${{ github.ref == 'refs/heads/beta' }}
echo isMain: ${{ github.ref == 'refs/heads/main' }}

View File

@@ -5,26 +5,16 @@ on:
types:
- published
workflow_dispatch:
inputs:
tag:
description: 'Release tag to process (optional)'
required: false
default: ''
env:
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
post_release:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Only needed if debugging on a branch other than default
# ref: ${{ github.event.release.target_commitish || github.ref }}
- uses: ./.github/actions/release-commenter
continue-on-error: true
env:
@@ -38,70 +28,3 @@ jobs:
comment-template: |
🚀 This is included in version {release_link}
update_templates:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Update template lockfiles and migrations
run: pnpm script:gen-templates
- name: Determine Release Tag
id: determine_tag
run: |
if [ "${{ github.event.inputs.tag }}" != "" ]; then
echo "Using tag from input: ${{ github.event.inputs.tag }}"
echo "release_tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "Using tag from release event: ${{ github.event.release.tag_name }}"
echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Commit and push changes
id: commit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -ex
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
export BRANCH_NAME=chore/templates-${{ steps.determine_tag.outputs.release_tag }}
git checkout -b $BRANCH_NAME
git add -A
# If no files have changed, exit early with success
git diff --cached --quiet --exit-code && exit 0
git commit -m "chore(templates): bump lockfiles after ${{ steps.determine_tag.outputs.release_tag }}"
git push origin $BRANCH_NAME
echo "committed=true" >> "$GITHUB_OUTPUT"
echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
- name: Debug Branches
run: |
echo "Target Commitish: ${{ github.event.release.target_commitish }}"
echo "Branch: ${{ steps.commit.outputs.branch }}"
echo "Ref: ${{ github.ref }}"
- name: Create pull request
uses: peter-evans/create-pull-request@v7
if: steps.commit.outputs.committed == 'true'
with:
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'area: templates'
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
commit-message: 'Automated update after release'
branch: ${{ steps.commit.outputs.branch }}
base: ${{ github.event_name != 'workflow_dispatch' && github.event.release.target_commitish || github.ref }}
title: 'chore(templates): bump lockfiles after ${{ steps.determine_tag.outputs.release_tag }}'
body: 'Automated bump of template lockfiles after release ${{ steps.determine_tag.outputs.release_tag }}'

View File

@@ -7,8 +7,6 @@
&nbsp;
<a href="https://discord.gg/payload"><img alt="Discord" src="https://img.shields.io/discord/967097582721572934?label=Discord&color=7289da&style=flat-square" /></a>
&nbsp;
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/dw/payload?style=flat-square" /></a>
&nbsp;
<a href="https://www.npmjs.com/package/payload"><img alt="npm" src="https://img.shields.io/npm/v/payload?style=flat-square" /></a>
&nbsp;
<a href="https://twitter.com/payloadcms"><img src="https://img.shields.io/badge/follow-payloadcms-1DA1F2?logo=twitter&style=flat-square" alt="Payload Twitter" /></a>
@@ -54,6 +52,10 @@ Jumpstart your next project by starting with a pre-made template. These are prod
Build any kind of website, blog, or portfolio from small to enterprise. Comes with a fully functional front-end built with RSCs and Tailwind.
### [🛒 E-Commerce](https://github.com/payloadcms/payload/tree/beta/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.
We're constantly adding more templates to our [Templates Directory](https://github.com/payloadcms/payload/tree/beta/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/beta/templates)

View File

@@ -31,7 +31,7 @@ The following options are available:
| **`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 details](../hooks/collections). |
| **`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. A field with `virtual: true` cannot be used as the title. |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#components). |
| **`defaultColumns`** | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this Collection. |
| **`enableRichTextLink`** | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
@@ -39,12 +39,11 @@ The following options are available:
| **`meta`** | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](./metadata). |
| **`preview`** | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#components). |
| **`listSearchableFields`** | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| **`pagination`** | Set pagination-specific options for this Collection. [More details](#pagination). |
| **`baseListFilter`** | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
### Custom Components
### Components
Collections can set their own [Custom Components](./components) which only apply to [Collection](../configuration/collections)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.

View File

@@ -6,7 +6,7 @@ desc: Fully customize your Admin Panel by swapping in your own React components.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Payload [Admin Panel](./overview) is designed to be as minimal and straightforward as possible to allow for easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
The Payload [Admin Panel](./overview) is designed to be as minimal and straightforward as possible to allow for both easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api/overview) directly on the front-end. Custom Components are available for nearly every part of the Admin Panel for extreme granularity and control.
@@ -18,45 +18,51 @@ All Custom Components in Payload are [React Server Components](https://react.dev
There are four main types of Custom Components in Payload:
- [Root Components](#root-components)
- [Collection Components](./collections#custom-components)
- [Global Components](./globals#custom-components)
- [Field Components](./fields#custom-components)
- [Collection Components](./collections#components)
- [Global Components](./globals#components)
- [Field Components](./fields)
To swap in your own Custom Component, first consult the list of available components, determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
To swap in your own Custom Component, consult the list of available components. Determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
## Defining Custom Components
As Payload compiles the Admin Panel, it checks your config for Custom Components. When detected, Payload either replaces its own default component with yours, or if none exists by default, renders yours outright. While are many places where Custom Components are supported in Payload, each is defined in the same way using [Component Paths](#component-paths).
## Defining Custom Components in the Payload Config
To add a Custom Component, point to its file path in your Payload Config:
In the Payload Config, you can define custom React Components to enhance the admin interface. However, these components should not be imported directly into the server-only Payload Config to avoid including client-side code. Instead, you specify the path to the component. Heres how you can do it:
src/components/Logout.tsx
```tsx
'use client'
import React from 'react'
export const MyComponent = () => {
return (
<button>Click me!</button>
)
}
```
payload.config.ts:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
admin: { // highlight-line
components: {
logout: {
Button: '/src/components/Logout#MyComponent' // highlight-line
Button: '/src/components/Logout#MyComponent'
}
}
},
})
```
<Banner type="success">
<strong>Note:</strong>
All Custom Components can be either Server Components or Client Components, depending on the presence of the `use client` directive at the top of the file.
</Banner>
In the path `/src/components/Logout#MyComponent`, `/src/components/Logout` is the file path, and `MyComponent` is the named export. If the component is the default export, the export name can be omitted. Path and export name are separated by a `#`.
### Component Paths
### Configuring the Base Directory
In order to ensure the Payload Config is fully Node.js compatible and as lightweight as possible, components are not directly imported into your config. Instead, they are identified by their file path for the Admin Panel to resolve on its own.
Component Paths, by default, are relative to your project's base directory. This is either your current working directory, or the directory specified in `config.admin.baseDir`. To simplify Component Paths, you can also configure the base directory using the `admin.importMap.baseDir` property.
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, this can be omitted.
Component paths, by default, are relative to your working directory - this is usually where your Next.js config lies. To simplify component paths, you have the option to configure the *base directory* using the `admin.baseDir.baseDir` property:
```ts
import { buildConfig } from 'payload'
@@ -67,72 +73,137 @@ const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'), // highlight-line
admin: { // highlight-line
importMap: {
baseDir: path.resolve(dirname, 'src'),
},
components: {
logout: {
Button: '/components/Logout#MyComponent' // highlight-line
Button: '/components/Logout#MyComponent'
}
}
},
})
```
In this example, we set the base directory to the `src` directory, and omit the `/src/` part of our component path string.
In this example, we set the base directory to the `src` directory - thus we can omit the `/src/` part of our component path string.
### Config Options
### Passing Props
While Custom Components are usually defined as a string, you can also pass in an object with additional options:
Each React Component in the Payload Config is typed as `PayloadComponent`. This usually is a string, but can also be an object containing the following properties:
| Property | Description |
|---------------|-------------------------------------------------------------------------------------------------------------------------------|
| `clientProps` | Props to be passed to the React Component if it's a Client Component |
| `exportName` | Instead of declaring named exports using `#` in the component path, you can also omit them from `path` and pass them in here. |
| `path` | Path to the React Component. Named exports can be appended to the end of the path, separated by a `#` |
| `serverProps` | Props to be passed to the React Component if it's a Server Component |
To pass in props from the config, you can use the `clientProps` and/or `serverProps` properties. This alleviates the need to use an HOC (Higher-Order-Component) to declare a React Component with props passed in.
Here is an example:
src/components/Logout.tsx
```tsx
'use client'
import React from 'react'
export const MyComponent = ({ text }: { text: string }) => {
return (
<button>Click me! {text}</button>
)
}
```
payload.config.ts:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
admin: { // highlight-line
components: {
logout: {
// highlight-start
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
clientProps: {
text: 'Some Text.'
},
exportName: 'MyComponent'
}
// highlight-end
}
}
},
})
```
The following options are available:
### Import Maps
| Property | Description |
|---------------|-------------------------------------------------------------------------------------------------------------------------------|
| **`clientProps`** | Props to be passed to the Custom Components if it's a Client Component. [More details](#custom-props). |
| **`exportName`** | Instead of declaring named exports using `#` in the component path, you can also omit them from `path` and pass them in here. |
| **`path`** | File path to the Custom Component. Named exports can be appended to the end of the path, separated by a `#`. |
| **`serverProps`** | Props to be passed to the Custom Component if it's a Server Component. [More details](#custom-props). |
It's essential to understand how `PayloadComponent` paths function behind the scenes. Directly importing React Components into your Payload Config using import statements can introduce client-only modules like CSS into your server-only config. This could error when attempting to load the Payload Config in server-only environments and unnecessarily increase the size of the Payload Config, which should remain streamlined and efficient for server use.
For more details on how to build Custom Components, see [Building Custom Components](#building-custom-components).
Instead, we utilize component paths to reference React Components. This method enhances the Payload Config with actual React Component imports on the client side, without affecting server-side usage. A script is deployed to scan the Payload Config, collecting all component paths and creating an `importMap.js`. This file, located in app/(payload)/admin/importMap.js, must be statically imported by your Next.js root page and layout. The script imports all the React Components from the specified paths into a Map, associating them with their respective paths (the ones you defined).
### Import Map
When constructing the `ClientConfig`, Payload uses the component paths as keys to fetch the corresponding React Component imports from the Import Map. It then substitutes the `PayloadComponent` with a `MappedComponent`. A `MappedComponent` includes the React Component and additional metadata, such as whether it's a server or a client component and which props it should receive. These components are then rendered through the `<RenderComponent />` component within the Payload Admin Panel.
In order for Payload to make use of [Component Paths](#component-paths), an "Import Map" is automatically generated at `app/(payload)/admin/importMap.js`. This file contains every Custom Component in your config, keyed to their respective paths. When Payload needs to lookup a component, it uses this file to find the correct import.
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](../local-api/outside-nextjs#troubleshooting) section.
The Import Map is automatically regenerated at startup and whenever Hot Module Replacement (HMR) runs, or you can run `payload generate:importmap` to manually regenerate it.
### Component paths in external packages
#### Custom Imports
Component paths are resolved relative to your project's base directory, which is either your current working directory or the directory specified in `config.admin.baseDir`. When using custom components from external packages, you can't use relative paths. Instead, use an import path that's accessible as if you were writing an import statement in your project's base directory.
If needed, custom items can be appended onto the Import Map. This is mostly only relevant for plugin authors who need to add a custom import that is not referenced in a known location.
To add a custom import to the Import Map, use the `admin.dependencies` property in your [Payload Config](../getting-started/overview):
For example, to export a field with a custom component from an external package named `my-external-package`:
```ts
import { buildConfig } from 'payload'
import type { Field } from 'payload'
export const MyCustomField: Field = {
type: 'text',
name: 'MyField',
admin: {
components: {
Field: 'my-external-package/client#MyFieldComponent'
}
}
}
```
export default buildConfig({
Despite `MyFieldComponent` living in `src/components/MyFieldComponent.tsx` in `my-external-package`, this will not be accessible from the consuming project. Instead, we recommend exporting all custom components from one file in the external package. For example, you can define a `src/client.ts file in `my-external-package`:
```ts
'use client'
export { MyFieldComponent } from './components/MyFieldComponent'
```
Then, update the package.json of `my-external-package:
```json
{
...
"exports": {
"./client": {
"import": "./dist/client.js",
"types": "./dist/client.d.ts",
"default": "./dist/client.js"
}
}
}
```
This setup allows you to specify the component path as `my-external-package/client#MyFieldComponent` as seen above. The import map will generate:
```ts
import { MyFieldComponent } from 'my-external-package/client'
```
which is a valid way to access MyFieldComponent that can be resolved by the consuming project.
### Custom Components from unknown locations
By default, any component paths from known locations are added to the import map. However, if you need to add any components from unknown locations to the import map, you can do so by adding them to the `admin.dependencies` array in your Payload Config. This is mostly only relevant for plugin authors and not for regular Payload users.
Example:
```ts
export default {
// ...
admin: {
// ...
@@ -149,12 +220,117 @@ export default buildConfig({
}
```
This way, `TestComponent` is added to the import map, no matter if it's referenced in a known location or not. On the client, you can then use the component like this:
```tsx
'use client'
import { RenderComponent, useConfig } from '@payloadcms/ui'
import React from 'react'
export const CustomView = () => {
const { config } = useConfig()
return (
<div>
<RenderComponent mappedComponent={config.admin.dependencies?.myTestComponent} />
</div>
)
}
```
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
<strong>Note:</strong>
You can also use set [Collection Components](./collections#components) and [Global Components](./globals#components) in their respective configs.
</Banner>
### Custom Providers
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export const MyProvider: React.FC = ({ children }) => {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
}
export const useMyCustomContext = () => useContext(MyCustomContext)
```
<Banner type="warning">
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
</Banner>
## Building Custom Components
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api/overview) directly on the front-end, among other things.
### Default Props
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself.
Here is an example:
@@ -183,46 +359,12 @@ Each Custom Component receives the following props by default:
| `payload` | The [Payload](../local-api/overview) class. |
| `i18n` | The [i18n](../configuration/i18n) object. |
<Banner type="warning">
<strong>Reminder:</strong>
All Custom Components also receive various other props that are specific component being rendered. See [Root Components](#root-components), [Collection Components](#collection-components), [Global Components](#global-components), or [Field Components](#custom-field-components) for a complete list of all default props per component.
Custom Components also receive various other props that are specific to the context in which the Custom Component is being rendered. For example, [Custom Views](./views) receive the `user` prop. For a full list of available props, consult the documentation related to the specific component you are working with.
<Banner type="success">
See [Root Components](#root-components), [Collection Components](#collection-components), [Global Components](#global-components), or [Field Components](#custom-field-components) for a complete list of all available components.
</Banner>
### Custom Props
To pass in custom props from the config, you can use either the `clientProps` or `serverProps` properties depending on whether your prop is [serializable](https://react.dev/reference/rsc/use-client#serializable-types), and whether your component is a Server or Client Component.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: { // highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!' // highlight-line
},
}
}
}
},
})
```
```tsx
'use client'
import React from 'react'
export const MyComponent = ({ myCustomProp }: { myCustomProp: string }) => {
return (
<button>{myCustomProp}</button>
)
}
```
### Client Components
When [Building Custom Components](#building-custom-components), it's still possible to use client-side code such as `useState` or the `window` object. To do this, simply add the `use client` directive at the top of your file. Payload will automatically detect and remove all default, [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component.
@@ -272,7 +414,6 @@ But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/us
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](./hooks#useconfig) hook:
```tsx
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
@@ -291,13 +432,14 @@ export const MyClientComponent: React.FC = () => {
See [Using Hooks](#using-hooks) for more details.
</Banner>
All [Field Components](./fields) automatically receive their respective Field Config through props.
All [Field Components](./fields) automatically receive their respective Field Config through a common [`field`](./fields#the-field-prop) prop:
```tsx
'use client'
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({ field: { name } }) => {
export const MyClientFieldComponent: TextFieldClientComponent = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
@@ -306,6 +448,28 @@ export const MyClientFieldComponent: TextFieldServerComponent = ({ field: { name
}
```
### Using Hooks
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts:
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Getting the Current Language
All Custom Components can support multiple languages to be consistent with Payload's [Internationalization](../configuration/i18n). To do this, first add your translation resources to the [I18n Config](../configuration/i18n).
@@ -328,7 +492,6 @@ export default async function MyServerComponent({ i18n }) {
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
@@ -372,7 +535,6 @@ export default async function MyServerComponent({ payload, locale }) {
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
@@ -394,29 +556,7 @@ const Greeting: React.FC = () => {
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Using Hooks
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can one of the many hooks available depending on your needs.
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Adding Styles
### Styling Custom Components
Payload has a robust [CSS Library](./customizing-css) that you can use to style your Custom Components similarly to Payload's built-in styling. This will ensure that your Custom Components match the existing design system, and so that they automatically adapt to any theme changes that might occur.
@@ -452,99 +592,10 @@ Payload also exports its [SCSS](https://sass-lang.com) library for reuse which i
background-color: var(--theme-elevation-900);
}
}
```
<Banner type="success">
<strong>Note:</strong>
You can also drill into Payload's own component styles, or easily apply global, app-wide CSS. More on that [here](./customizing-css).
</Banner>
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
<strong>Note:</strong>
You can also use set [Collection Components](./collections#custom-components) and [Global Components](./globals#custom-components) in their respective configs.
</Banner>
### Custom Providers
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export const MyProvider: React.FC = ({ children }) => {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
}
export const useMyCustomContext = () => useContext(MyCustomContext)
```
<Banner type="warning">
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
</Banner>

View File

@@ -6,7 +6,7 @@ desc:
keywords:
---
[Fields](../fields/overview) within the [Admin Panel](./overview) can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#custom-components), [Conditional Logic](#conditional-logic), [Custom Validations](../fields/overview#validation), and more.
[Fields](../fields/overview) within the [Admin Panel](./overview) can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#field-components), [Conditional Logic](#conditional-logic), [Custom Validations](../fields/overview#validation), and more.
For example, your app might need to render a specific interface that Payload does not inherently support, such as a color picker. To do this, you could replace the default [Text Field](../fields/text) input with your own user-friendly component that formats the data into a valid color value.
@@ -56,7 +56,334 @@ The following options are available:
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
## Field Descriptions
## Field Components
Within the [Admin Panel](./overview), fields are rendered in three distinct places:
- [Field](#the-field-component) - The actual form field rendered in the Edit View.
- [Cell](#the-cell-component) - The table cell component rendered in the List View.
- [Filter](#the-filter-component) - The filter component rendered in the List View.
To easily swap in Field Components with your own, use the `admin.components` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
]
}
```
The following options are available:
| Component | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#the-field-component). |
| **`Cell`** | The table cell rendered of the List View. [More details](#the-cell-component). |
| **`Filter`** | The filter component rendered in the List View. [More details](#the-filter-component). || Component | Description |
| **`Label`** | Override the default Label of the Field Component. [More details](#the-label-component). |
| **`Error`** | Override the default Error of the Field Component. [More details](#the-error-component). |
| **`Description`** | Override the default Description of the Field Component. [More details](#the-description-component). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput).|
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
_\* **`beforeInput`** and **`afterInput`** are only supported in fields that do not contain other fields, such as [`Text`](../fields/text), and [`Textarea`](../fields/textarea)._
### The Field Component
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To easily swap in your own Field Component, use the `admin.components.Field` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
<Banner type="warning">
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#the-label-component), [`Error`](#the-error-component), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
</Banner>
All Field Components receive the following props:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### Sending and receiving values from the form
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](./hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](./hooks) documentation. For additional help, see [Building Custom Components](./components#building-custom-components).
</Banner>
#### TypeScript
When building Custom Field Components, you can import the component type to ensure type safety. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and for every client/server environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### The `field` Prop
All Field Components are passed their own Field Config through a common `field` prop. Within Server Components, this is the original Field Config as written within your Payload Config. Within Client Components, however, this is a "Client Config", which is a sanitized, client-friendly version of the Field Config. This is because the original Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), meaning it cannot be passed into Client Components without first being transformed.
The Client Field Config is an exact copy of the original Field Config, minus all non-serializable properties, plus all evaluated functions such as field labels, [Custom Components](../components), etc.
Server Component:
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyServerField: TextFieldServerComponent = ({ clientField }) => {
return <TextField field={clientField} />
}
```
<Banner type="info">
<strong>Tip:</strong>
Server Components can still access the original Field Config through the `field` prop.
</Banner>
Client Component:
```tsx
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const MyTextField: TextFieldClientComponent = ({ field }) => {
return <TextField field={field} />
}
```
The following additional properties are also provided to the `field` prop:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`_path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray[0].myField`. |
| **`_schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `myGroup.myArray.myField` |
<Banner type="info">
<strong>Note:</strong>
These properties are underscored to denote that they are not part of the original Field Config, and instead are attached during client sanitization to make fields easier to work with on the front-end.
</Banner>
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### The Cell Component
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To easily swap in your own Cell Component, use the `admin.components.Cell` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
All Cell Components receive the following props:
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
<Banner type="info">
<strong>Tip:</strong>
Use the [`useTableCell`](./hooks#usetablecell) hook to subscribe to the field's `cellData` and `rowData`.
</Banner>
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
### The Label Component
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To easily swap in your own Label Component, use the `admin.components.Label` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
Custom Label Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
### The Error Component
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To easily swap in your own Error Component, use the `admin.components.Error` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
Custom Error Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| --------------- | ------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
### The Description Property
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
@@ -64,7 +391,7 @@ A description can be configured in three ways:
- As a string.
- As a function which returns a string. [More details](#description-functions).
- As a React component. [More details](#description).
- As a React component. [More details](#the-description-component).
To easily add a Custom Description to a field, use the `admin.description` property in your [Field Config](../fields/overview):
@@ -88,7 +415,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
<Banner type="warning">
<strong>Reminder:</strong>
To replace the Field Description with a [Custom Component](./components), use the `admin.components.Description` property. [More details](#description).
To replace the Field Description with a [Custom Component](./components), use the `admin.components.Description` property. [More details](#the-description-component).
</Banner>
#### Description Functions
@@ -121,6 +448,89 @@ All Description Functions receive the following arguments:
| -------------- | ---------------------------------------------------------------- |
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
### The Description Component
Alternatively to the [Description Property](#the-description-property), you can also use a [Custom Component](./components) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To easily add a Description Component to a field, use the `admin.components.Description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
}
```
_For details on how to build a Custom Description, see [Building Custom Components](./components#building-custom-components)._
Custom Description Components receive all [Field Component](#the-field-component) props, plus the following props:
| Property | Description |
| -------------- | ---------------------------------------------------------------- |
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
<Banner type="success">
<strong>Reminder:</strong>
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
### afterInput and beforeInput
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
## Conditional Logic
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes three arguments:
@@ -159,322 +569,3 @@ The `condition` function should return a boolean that will control if the field
]
}
```
## Custom Components
Within the [Admin Panel](./overview), fields are represented in three distinct places:
- [Field](#field) - The actual form field rendered in the Edit View.
- [Cell](#cell) - The table cell component rendered in the List View.
- [Filter](#filter) - The filter component rendered in the List View.
To easily swap in Field Components with your own, use the `admin.components` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
]
}
```
The following options are available:
| Component | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput).|
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
### Field
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To easily swap in your own Field Component, use the `admin.components.Field` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
<Banner type="warning">
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#label), [`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
</Banner>
#### Default Props
All Field Components receive the following props by default:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
| **`schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `posts.myGroup.myArray.myField`. |
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
In addition to the above props, all Server Components will also receive the following props:
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`clientField`** | The serializable Client Field Config. |
| **`field`** | The Field Config. [More details](../fields/overview). |
| **`data`** | The current document being edited. |
| **`i18n`** | The [i18n](../configuration/i18n) object.
| **`payload`** | The [Payload](../local-api/overview) class. |
| **`permissions`** | The field permissions based on the currently authenticated user. |
| **`siblingData`** | The data of the field's siblings. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`value`** | The value of the field at render-time. |
#### Sending and receiving values from the form
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](./hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](./hooks) documentation. For additional help, see [Building Custom Components](./components#building-custom-components).
</Banner>
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### Cell
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To easily swap in your own Cell Component, use the `admin.components.Cell` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
All Cell Components receive the same [Default Field Component Props](#field), plus the following:
| Property | Description |
| ---------------- | ----------------------------------------------------------------- |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
### Label
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To easily swap in your own Label Component, use the `admin.components.Label` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
All Custom Label Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Label Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
### Description
Alternatively to the [Description Property](#the-description-property), you can also use a [Custom Component](./components) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To easily add a Description Component to a field, use the `admin.components.Description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
}
```
All Custom Description Components receive the same [Default Field Component Props](#field).
For details on how to build a Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
### Error
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To easily swap in your own Error Component, use the `admin.components.Error` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
All Error Components receive the [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Error Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
### afterInput and beforeInput
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
}
```
All `afterInput` and `beforeInput` Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components).

View File

@@ -29,13 +29,13 @@ The following options are available:
| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **`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 details](#custom-components). |
| **`components`** | Swap in your own React components to be used within this Global. [More details](#components). |
| **`preview`** | Function to generate a preview URL within the Admin Panel for this Global that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this collection. |
| **`meta`** | Page metadata overrides to apply to this Global within the Admin Panel. [More details](./metadata). |
### Custom Components
### Components
Globals can set their own [Custom Components](./components) which only apply to [Global](../configuration/globals)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.

View File

@@ -21,11 +21,10 @@ To do so, import the `useField` hook as follows:
```tsx
'use client'
import type { TextFieldClientComponent } from 'payload'
import { useField } from '@payloadcms/ui'
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
const { value, setValue } = useField({ path }) // highlight-line
const CustomTextField: React.FC = () => {
const { value, setValue, path } = useField() // highlight-line
return (
<div>
@@ -45,7 +44,7 @@ The `useField` hook accepts the following arguments:
| Property | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path` | If you do not provide a `path`, `name` will be used instead. This is the path to the field in the form data. |
| `path` | If you do not provide a `path` or a `name`, this hook will look for one using the [`useFieldProps`](#usefieldprops) hook. |
| `validate` | A validation function executed client-side _before_ submitting the form to the server. Different than [Field-level Validation](../fields/overview#validation) which runs strictly on the server. |
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
@@ -73,6 +72,32 @@ type FieldType<T> = {
}
```
## useFieldProps
[Custom Field Components](./fields#the-field-component) can be rendered on the server. When using a server component as a custom field component, you can access dynamic props from within any client component rendered by your custom server component. This is done using the `useFieldProps` hook. This is important because some fields can be dynamic, such as when nested in an [`array`](../fields/array) or [`blocks`](../fields/block) field. For example, items can be added, re-ordered, or deleted on-the-fly.
You can use the `useFieldProps` hooks to access dynamic props like `path`:
```tsx
'use client'
import { useFieldProps } from '@payloadcms/ui'
const CustomTextField: React.FC = () => {
const { path } = useFieldProps() // highlight-line
return (
<div>
{path}
</div>
)
}
```
<Banner type="success">
<strong>Tip:</strong>
The [`useField`](#usefield) hook calls the `useFieldProps` hook internally, so you don't need to use both in the same component unless explicitly needed.
</Banner>
## useFormFields
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
@@ -875,6 +900,27 @@ const MyComponent: React.FC = () => {
}
```
## useTableCell
Similar to [`useFieldProps`](#usefieldprops), all [Custom Cell Components](./fields#the-cell-component) are rendered on the server, and as such, only have access to static props at render time. But, some props need to be dynamic, such as the field value itself.
For this reason, dynamic props like `cellData` are managed in their own React context, which can be accessed using the `useTableCell` hook.
```tsx
'use client'
import { useTableCell } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
const { cellData } = useTableCell() // highlight-line
return (
<div>
{cellData}
</div>
)
}
```
## useDocumentEvents
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:

View File

@@ -195,7 +195,7 @@ app/
<Banner type="warning">
<strong>Note:</strong>
If you set Root-level Routes _before_ auto-generating the Admin Panel via `create-payload-app`, your [Project Structure](#project-structure) will already be set up correctly.
If you set Root-level Routes _before_ auto-generating the Admin Panel, your [Project Structure](#project-structure) will already be set up correctly.
</Banner>
### Admin-level Routes

View File

@@ -15,7 +15,7 @@ There are four types of views within the Admin Panel:
- [Global Views](#global-views)
- [Document Views](#document-views)
To swap in your own Custom View, first consult the list of available components, determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-views) accordingly.
To swap in your own Custom Views, consult the list of available components. Determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-views) accordingly.
## Root Views

View File

@@ -65,7 +65,7 @@ export default buildConfig({
},
],
defaultLocale: 'en', // required
fallback: true, // defaults to true
fallback: true,
},
})
```
@@ -81,7 +81,7 @@ The following options are available:
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`defaultLocale`** | Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated unless a fallback is explicitly provided in the request. True by default. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated. |
### Locales

View File

@@ -8,7 +8,7 @@ keywords: overview, config, configuration, documentation, Content Management Sys
Payload is a _config-based_, code-first CMS and application framework. The Payload Config is central to everything that Payload does, allowing for deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon.
Everything from your [Database](../database/overview) choice to the appearance of the [Admin Panel](../admin/overview) is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
Everything from your [Database](../database/overview) choice, to the appearance of the [Admin Panel](../admin/overview), is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
The Payload Config is a `payload.config.ts` file typically located in the root of your project:
@@ -29,7 +29,7 @@ The Payload Config is strongly typed and ties directly into Payload's TypeScript
## Config Options
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data through [Fields](../fields/overview).
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data.
Here is one of the simplest possible Payload configs:
@@ -76,7 +76,6 @@ The following options are available:
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |

View File

@@ -6,7 +6,7 @@ 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, nextjs
---
The Array Field is used when you need to have a set of "repeating" [Fields](./overview). It stores an array of objects containing fields that you define. These fields can be of any type, including other arrays, to achieve infinitely nested data structures.
The Array Field is used when you need to have a set of "repeating" [Fields](./overview). It stores an array of objects containing fields that you define. These fields can be of any type, including other arrays to achieve infinitely nested structures.
Arrays are useful for many different types of content from simple to complex, such as:

View File

@@ -84,51 +84,6 @@ The Blocks Field inherits all of the default options from the base [Field Admin
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
#### Customizing the way your block is rendered in Lexical
If you're using this block within the [Lexical editor](/docs/lexical/overview), you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.
- `admin.components.Label` - pass a custom React component here to customize the way that the label is rendered for this block
- `admin.components.Block` - pass a component here to completely override the way the block is rendered in Lexical with your own component
This is super handy if you'd like to present your editors with a very deliberate and nicely designed block "preview" right in your rich text.
For example, if you have a `gallery` block, you might want to actually render the gallery of images directly in your Lexical block. With the `admin.components.Block` property, you can do exactly that!
<Banner type="success">
<strong>Tip:</strong><br/>
If you customize the way your block is rendered in Lexical, you can import utility components to easily edit / remove your block - so that you don't have to build all of this yourself.
</Banner>
To import these utility components for one of your custom blocks, you can import the following:
```ts
import {
// Edit block buttons (choose the one that corresponds to your usage)
// When clicked, this will open a drawer with your block's fields
// so your editors can edit them
InlineBlockEditButton,
BlockEditButton,
// Buttons that will remove this block from Lexical
// (choose the one that corresponds to your usage)
InlineBlockRemoveButton,
BlockRemoveButton,
// The label that should be rendered for an inline block
InlineBlockLabel,
// The default "container" that is rendered for an inline block
// if you want to re-use it
InlineBlockContainer,
// The default "collapsible" UI that is rendered for a regular block
// if you want to re-use it
BlockCollapsible,
} from '@payloadcms/richtext-lexical/client'
```
## Block Configs
Blocks are defined as separate configs of their own.

View File

@@ -86,7 +86,7 @@ _\* This property is passed directly to [react-datepicker](https://github.com/Ha
These properties only affect how the date is displayed in the UI. The full date is always stored in the format `YYYY-MM-DDTHH:mm:ss.SSSZ` (e.g. `1999-01-01T8:00:00.000+05:00`).
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v4.1.0/docs/format].
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v2.29.3/docs/format].
`pickerAppearance` sets the appearance of the **react datepicker**, the options available are `dayAndTime`, `dayOnly`, `timeOnly`, and `monthOnly`. By default, the datepicker will display `dayOnly`.

View File

@@ -147,7 +147,7 @@ You can control the user experience of the join field using the `admin` config p
| Option | Description |
|------------------------|----------------------------------------------------------------------------------------|
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
| **`components.Label`** | Override the default Label of the Field Component. [More details](../admin/fields#label) |
| **`components.Label`** | Override the default Label of the Field Component. [More details](#the-label-component). |
## Join Field Data

View File

@@ -7,7 +7,7 @@ desc: The JSON field type will store any string in the Database. Learn how to us
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The JSON Field saves raw JSON to the database and provides the [Admin Panel](../admin/overview) with a code editor styled interface. This is different from the [Code Field](./code) which saves the value as a string in the database.
The JSON Field saves actual JSON in the database, which differs from the Code field that saves the value as a string in the database.
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/json.png"

View File

@@ -93,14 +93,13 @@ Presentational Fields do not store data in the database. Instead, they are used
Here are the available Presentational Fields:
- [Collapsible](/docs/fields/collapsible) - nests fields within a collapsible component
- [Join](/docs/fields/join) - achieves two-way data binding between fields
- [Row](/docs/fields/row) - aligns fields horizontally
- [Tabs (Unnamed)](/docs/fields/tabs) - nests fields within a tabbed layout
- [UI](/docs/fields/ui) - blank field for custom UI components
<Banner type="warning">
<strong>Tip:</strong>
Don't see a Field Type that fits your needs? You can build your own using a [Custom Field Component](../admin/fields#field).
Don't see a Field Type that fits your needs? You can build your own using a [Custom Field Component](../admin/fields#the-field-component).
</Banner>
## Field Options
@@ -124,7 +123,7 @@ export const MyField: Field = {
### Field Names
All [Data Fields](#data-fields) require a `name` property. This is the key that will be used to store and retrieve the field's value in the database. This property must be unique amongst this field's siblings.
All [Data Fields](#data-fields) require a `name` property. This is the key that will be used to store and retrieve the field's value in the database. This property must be unique within the Collection, Global, or nested group that it is defined in.
To set a field's name, use the `name` property in your Field Config:
@@ -206,7 +205,7 @@ export const MyField: Field = {
}
```
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's [Database Adapters](../database/overview) will apply it to the database schema or models.
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's DB adapters will apply it to the database schema or models.
Functions can be written to make use of the following argument properties:
@@ -265,7 +264,7 @@ The following arguments are provided to the `validate` function:
#### Validation Context
The `ctx` argument contains full document data, sibling field data, the current operation, and other useful information such as currently authenticated user:
The `ctx` argument contains full document data, sibling field data, the current operation, and other useful information such as currently authenticated in user:
```ts
import type { Field } from 'payload'
@@ -359,7 +358,7 @@ For full details on Admin Options, see the [Field Admin Options](../admin/fields
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically.
To define a custom ID field, add a top-level field with the `name` property set to `id`:
To define a custom ID field, add a new field with the `name` property set to `id`:
```ts
import type { CollectionConfig } from 'payload'

View File

@@ -73,59 +73,6 @@ export const ExampleCollection: CollectionConfig = {
}
```
## Querying - near
## Querying
In order to do query based on the distance to another point, you can use the `near` operator. When querying using the near operator, the returned documents will be sorted by nearest first.
## Querying - within
In order to do query based on whether points are within a specific area defined in GeoJSON, you can use the `within` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: "points",
where: {
point: {
within: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```
## Querying - intersects
In order to do query based on whether points intersect a specific area defined in GeoJSON, you can use the `intersects` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
[9.0, 21.0], // top-left
[11.0, 21.0], // top-right
[11.0, 19.0], // bottom-right
[9.0, 19.0], // back to starting point to close the polygon
]
payload.find({
collection: "points",
where: {
point: {
intersects: {
type: 'Polygon',
coordinates: [polygon],
},
},
},
})
```

View File

@@ -204,4 +204,4 @@ If you are looking to create a dynamic select field, the following tutorial will
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) docs.
If you want to learn more about custom components check out the [Admin > Custom Component](/docs/admin/components#field-component) docs.

View File

@@ -32,8 +32,8 @@ export const MyUIField: Field = {
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`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](../admin/components/#field) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field) |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field-component) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field-component) |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

View File

@@ -68,10 +68,15 @@ Here's a quick example of a React Server Component fetching data using the Local
```tsx
import React from 'react'
import config from '@payload-config'
import { getPayload } from 'payload'
import { getPayloadHMR } from '@payloadcms/next/utilities'
const MyServerComponent: React.FC = () => {
const payload = await getPayload({ config })
// If you're working in Next.js, and you want HMR,
// you should get Payload via the `getPayloadHMR` function.
const payload = await getPayloadHMR({ config })
// If you are writing a standalone script and do not need HMR,
// you can get Payload via import { getPayload } from 'payload' instead.
// The `findResult` here will be fully typed as `PaginatedDocs<Page>`,
// where you will have the `docs` that are returned as well as

View File

@@ -35,10 +35,6 @@ Adding Payload to an existing Next.js app is super straightforward. You can eith
If you don't have a Next.js app already, but you still want to start a project from a blank Next.js app, you can create a new Next.js app using `npx create-next-app` - and then just follow the steps below to install Payload.
<Banner type="info">
<strong>Note:</strong> Next.js version 15 or higher is required for Payload.
</Banner>
#### 1. Install the relevant packages
First, you'll want to add the required Payload packages to your project and can do so by running the command below:

View File

@@ -1,46 +0,0 @@
---
title: Jobs
label: Jobs
order: 40
desc: A Job is a set of work that is offloaded from your APIs and will be processed at a later date.
keywords: jobs queue, application framework, typescript, node, react, nextjs
---
Now that we have covered Tasks and Workflows, we can tie them together with a concept called a Job.
<Banner type="default">
Whereas you define Workflows and Tasks, which control your business logic, a <strong>Job</strong> is an individual instance of either a Task or a Workflow which contains many tasks.
</Banner>
For example, let's say we have a Workflow or Task that describes the logic to sync information from Payload to a third-party system. This is how you'd declare how to sync that info, but it wouldn't do anything on its own. In order to run that task or workflow, you'd create a Job that references the corresponding Task or Workflow.
Jobs are stored in the Payload database in the `payload-jobs` collection, and you can decide to keep a running list of all jobs, or configure Payload to delete the job when it has been successfully executed.
#### Queuing a new job
In order to queue a job, you can use the `payload.jobs.queue` function.
Here's how you'd queue a new Job, which will run a `createPostAndUpdate` workflow:
```ts
const createdJob = await payload.jobs.queue({
// Pass the name of the workflow
workflow: 'createPostAndUpdate',
// The input type will be automatically typed
// according to the input you've defined for this workflow
input: {
title: 'my title',
},
})
```
In addition to being able to queue new Jobs based on Workflows, you can also queue a job for a single Task:
```ts
const createdJob = await payload.jobs.queue({
task: 'createPost',
input: {
title: 'my title',
},
})
```

View File

@@ -6,65 +6,458 @@ desc: Payload provides all you need to run job queues, which are helpful to offl
keywords: jobs queue, application framework, typescript, node, react, nextjs
---
Payload's Jobs Queue gives you a simple, yet powerful way to offload large or future tasks to separate compute resources which is a very powerful feature of many application frameworks.
Payload's Jobs Queue gives you a simple, yet powerful way to offload large or future tasks to separate compute resources.
### Example use cases
For example, when building applications with Payload, you might run into a case where you need to perform some complex logic in a Payload [Hook](/docs/hooks/overview) but you don't want that hook to "block" or slow down the response returned from the Payload API.
**Non-blocking workloads**
Instead of running long or expensive logic in a Hook, you can instead create a Job and add it to a Queue. It can then be picked up by a separate worker which periodically checks the queue for new jobs, and then executes each job accordingly. This way, your Payload API responses can remain as fast as possible, and you can still perform logic as necessary without blocking or affecting your users' experience.
You might need to perform some complex, slow-running logic in a Payload [Hook](/docs/hooks/overview) but you don't want that hook to "block" or slow down the response returned from the Payload API. Instead of running this logic directly in a hook, which would block your API response from returning until the expensive work is completed, you can queue a new Job and let it run at a later date.
Jobs are also handy for delegating certain actions to take place in the future, such as scheduling a post to be published at a later date. In this example, you could create a Job that will automatically publish a post at a certain time.
Examples:
#### How it works
- Create vector embeddings from your documents, and keep them in sync as your documents change
- Send data to a third-party API on document change
- Trigger emails based on customer actions
There are a few concepts that you should become familiarized with before using Payload's Jobs Queue - [Tasks](#tasks), [Workflows](#workflows), [Jobs](#jobs), and finally [Queues](#queues).
**Scheduled actions**
## Tasks
If you need to schedule an action to be run or processed at a certain date in the future, you can queue a job with the `waitUntil` property set. This will make it so the job is not "picked up" until that `waitUntil` date has passed.
<Banner type="default">
A <strong>"Task"</strong> is a function definition that performs business logic and whose input and output are both strongly typed.
</Banner>
Examples:
You can register Tasks on the Payload config, and then create Jobs or Workflows that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
- Process scheduled posts, where the scheduled date is at a time set in the future
- Unpublish posts at a given time
- Send a reminder email to a customer after X days of signing up for a trial
Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried.
**Periodic sync or similar scheduled action**
Tasks can either be defined within the `jobs.tasks` array in your payload config, or they can be defined inline within a workflow.
Some applications may need to perform a regularly scheduled operation of some type. Jobs are perfect for this because you can execute their logic using `cron`, scheduled nightly, every twelve hours, or some similar time period.
### Defining tasks in the config
Examples:
Simply add a task to the `jobs.tasks` array in your Payload config. A task consists of the following fields:
- You'd like to send emails to all customers on a regular, scheduled basis
- Periodically trigger a rebuild of your frontend at night
- Sync resources to or from a third-party API during non-peak times
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this job. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the job. You can either pass a string-based path to the job function file, or the job function itself. If you are using large dependencies within your job, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this task. By default, this is "Task" + the capitalized task slug. |
| `outputSchema` | Define the output field schema - payload will generate a type for this schema. |
| `label` | Define a human-friendly label for this task. |
| `onFail` | Function to be executed if the task fails. |
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. |
**Offloading complex operations**
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks picks up a Job that includes this task.
You may run into the need to perform computationally expensive functions which might slow down your main Payload API server(s). The Jobs Queue allows you to offload these tasks a separate compute resource rather than slowing down the server(s) that run your Payload APIs. With Payload Task definitions, you can even keep large dependencies out of your main Next.js bundle by dynamically importing them only when they are used. This keeps your Next.js + Payload compilation fast and ensures large dependencies do not get bundled into your Payload production build.
It should return an object with an `output` key, which should contain the output of the task as you've defined.
Examples:
Example:
- You need to create (and then keep in sync) vector embeddings of your documents as they change, but you use an open source model to generate embeddings
- You have a PDF generator that needs to dynamically build and send PDF versions of documents to customers
- You need to use a headless browser to perform some type of logic
- You need to perform a series of actions, each of which depends on a prior action and should be run in as "durable" of a fashion as possible
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
// Configure this task to automatically retry
// up to two times
retries: 2,
### How it works
// This is a unique identifier for the task
There are a few concepts that you should become familiarized with before using Payload's Jobs Queue. We recommend learning what each of these does in order to fully understand how to leverage the power of Payload's Jobs Queue.
slug: 'createPost',
1. [Tasks](/docs/beta/jobs-queue/tasks)
1. [Workflows](/docs/beta/jobs-queue/workflows)
1. [Jobs](/docs/beta/jobs-queue/jobs)
1. [Queues](/docs/beta/jobs-queue/queues)
// These are the arguments that your Task will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
All of these pieces work together in order to allow you to offload long-running, expensive, or future scheduled work from your main APIs.
// These are the properties that the function should output
outputSchema: [
{
name: 'postID',
type: 'text',
required: true,
},
],
Here's a quick overview:
// This is the function that is run when the task is invoked
handler: async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
},
} as TaskConfig<'createPost'>,
]
}
})
```
- A Task is a specific function that performs business logic
- Workflows are groupings of specific tasks which should be run in-order, and can be retried from a specific point of failure
- A Job is an instance of a single task or workflow which will be executed
- A Queue is a way to segment your jobs into different "groups" - for example, some to run nightly, and others to run every 10 minutes
In addition to defining handlers as functions directly provided to your Payload config, you can also pass an _absolute path_ to where the handler is defined. If your task has large dependencies, and you are planning on executing your jobs in a separate process that has access to the filesystem, this could be a handy way to make sure that your Payload + Next.js app remains quick to compile and has minimal dependencies.
In general, this is an advanced use case. Here's how this would look:
`payload.config.ts:`
```ts
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
jobs: {
tasks: [
{
// ...
// The #createPostHandler is a named export within the `createPost.ts` file
handler: path.resolve(dirname, 'src/tasks/createPost.ts') + '#createPostHandler',
}
]
}
})
```
Then, the `createPost` file itself:
`src/tasks/createPost.ts:`
```ts
import type { TaskHandler } from 'payload'
export const createPostHandler: TaskHandler<'createPost'> = async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
}
```
## Workflows
<Banner type="default">
A <strong>"Workflow"</strong> is an optional way to <em>combine multiple tasks together</em> in a way that can be gracefully retried from the point of failure.
</Banner>
They're most helpful when you have multiple tasks in a row, and you want to configure each task to be able to be retried if they fail.
If a task within a workflow fails, the Workflow will automatically "pick back up" on the task where it failed and **not re-execute any prior tasks that have already been executed**.
#### Defining a workflow
The most important aspect of a Workflow is the `handler`, where you can declare when and how the tasks should run by simply calling the `runTask` function. If any task within the workflow, fails, the entire `handler` function will re-run.
However, importantly, tasks that have successfully been completed will simply re-return the cached and saved output without running again. The Workflow will pick back up where it failed and only task from the failure point onward will be re-executed.
To define a JS-based workflow, simply add a workflow to the `jobs.wokflows` array in your Payload config. A workflow consists of the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this workflow. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the workflow. You can either pass a string-based path to the workflow function file, or workflow job function itself. If you are using large dependencies within your workflow, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this workflow. By default, this is "Workflow" + the capitalized workflow slug. |
| `label` | Define a human-friendly label for this workflow. |
| `queue` | Optionally, define the queue name that this workflow should be tied to. Defaults to "default". |
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
// The arguments that the workflow will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
// The handler that defines the "control flow" of the workflow
// Notice how it calls `runTask` to execute tasks
handler: async ({ job, runTask }) => {
// This workflow first runs a task called `createPost`
const output = await runTask({
task: 'createPost',
// You need to define a unique ID for this task invocation
// that will always be the same if this workflow fails
// and is re-executed in the future
id: '1',
input: {
title: job.input.title,
},
})
// Once the prior task completes, it will run a task
// called `updatePost`
await runTask({
task: 'updatePost',
id: '2',
input: {
post: job.taskStatus.createPost['1'].output.postID, // or output.postID
title: job.input.title + '2',
},
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```
#### Running tasks inline
In the above example, our workflow was executing tasks that we already had defined in our Payload config. But, you can also run tasks without predefining them.
To do this, you can use the `runTaskInline` function.
The drawbacks of this approach are that tasks cannot be re-used across workflows as easily, and the **task data stored in the job** will not be typed. In the following example, the inline task data will be stored on the job under `job.taskStatus.inline['2']` but completely untyped, as types for dynamic tasks like these cannot be generated beforehand.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
handler: async ({ job, runTask }) => {
// Here, we run a predefined task.
// The `createPost` handler arguments and return type
// are both strongly typed
const output = await runTask({
task: 'createPost',
id: '1',
input: {
title: job.input.title,
},
})
// Here, this task is not defined in the Payload config
// and is "inline". Its output will be stored on the Job in the database
// however its arguments will be untyped.
const { newPost } = await runTaskInline({
task: async ({ req }) => {
const newPost = await req.payload.update({
collection: 'post',
id: '2',
req,
retries: 3,
data: {
title: 'updated!',
},
})
return {
output: {
newPost
},
}
},
id: '2',
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```
## Jobs
Now that we have covered Tasks and Workflows, we can tie them together with a concept called a Job.
<Banner type="default">
Whereas you define Workflows and Tasks, which control your business logic, a <strong>Job</strong> is an individual instance of either a Task or a Workflow which contains many tasks.
</Banner>
For example, let's say we have a Workflow or Task that describes the logic to sync information from Payload to a third-party system. This is how you'd declare how to sync that info, but it wouldn't do anything on its own. In order to run that task or workflow, you'd create a Job that references the corresponding Task or Workflow.
Jobs are stored in the Payload database in the `payload-jobs` collection, and you can decide to keep a running list of all jobs, or configure Payload to delete the job when it has been successfully executed.
#### Queuing a new job
In order to queue a job, you can use the `payload.jobs.queue` function.
Here's how you'd queue a new Job, which will run a `createPostAndUpdate` workflow:
```ts
const createdJob = await payload.jobs.queue({
// Pass the name of the workflow
workflow: 'createPostAndUpdate',
// The input type will be automatically typed
// according to the input you've defined for this workflow
input: {
title: 'my title',
},
})
```
In addition to being able to queue new Jobs based on Workflows, you can also queue a job for a single Task:
```ts
const createdJob = await payload.jobs.queue({
task: 'createPost',
input: {
title: 'my title',
},
})
```
## Queues
Now let's talk about how to _run these jobs_. Right now, all we've covered is how to queue up jobs to run, but so far, we aren't actually running any jobs. This is the final piece of the puzzle.
<Banner type="default">
A <strong>Queue</strong> is a list of jobs that should be executed in order of when they were added.
</Banner>
When you go to run jobs, Payload will query for any jobs that are added to the queue and then run them. By default, all queued jobs are added to the `default` queue.
**But, imagine if you wanted to have some jobs that run nightly, and other jobs which should run every five minutes.**
By specifying the `queue` name when you queue a new job using `payload.jobs.queue()`, you can queue certain jobs with `queue: 'nightly'`, and other jobs can be left as the default queue.
Then, you could configure two different runner strategies:
1. A `cron` that runs nightly, querying for jobs added to the `nightly` queue
2. Another that runs any jobs that were added to the `default` queue every ~5 minutes or so
## Executing jobs
As mentioned above, you can queue jobs, but the jobs won't run unless a worker picks up your jobs and runs them. This can be done in two ways:
#### Endpoint
You can execute jobs by making a fetch request to the `/api/payload-jobs/run` endpoint:
```ts
// Here, we're saying we want to run only 100 jobs for this invocation
// and we want to pull jobs from the `nightly` queue:
await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
});
```
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
**Vercel Cron Example**
If you're deploying on Vercel, you can add a `vercel.json` file in the root of your project that configures Vercel Cron to invoke the `run` endpoint on a cron schedule.
Here's an example of what this file will look like:
```json
{
"crons": [
{
"path": "/api/payload-jobs/run",
"schedule": "*/5 * * * *"
}
]
}
```
The configuration above schedules the endpoint `/api/payload-jobs/run` to be invoked every 5 minutes.
The last step will be to secure your `run` endpoint so that only the proper users can invoke the runner.
To do this, you can set an environment variable on your Vercel project called `CRON_SECRET`, which should be a random string—ideally 16 characters or longer.
Then, you can modify the `access` function for running jobs by ensuring that only Vercel can invoke your runner.
```ts
export default buildConfig({
// Other configurations...
jobs: {
access: {
run: ({ req }: { req: PayloadRequest }): boolean => {
// Allow logged in users to execute this endpoint (default)
if (req.user) return true
// If there is no logged in user, then check
// for the Vercel Cron secret to be present as an
// Authorization header:
const authHeader = req.headers.get('authorization');
return authHeader === `Bearer ${process.env.CRON_SECRET}`;
},
},
// Other job configurations...
}
})
```
This works because Vercel automatically makes the `CRON_SECRET` environment variable available to the endpoint as the `Authorization` header when triggered by the Vercel Cron, ensuring that the jobs can be run securely.
After the project is deployed to Vercel, the Vercel Cron job will automatically trigger the `/api/payload-jobs/run` endpoint in the specified schedule, running the queued jobs in the background.
#### Local API
If you want to process jobs programmatically from your server-side code, you can use the Local API:
```ts
const results = await payload.jobs.run()
// You can customize the queue name and limit by passing them as arguments:
await payload.jobs.run({ queue: 'nightly', limit: 100 })
```
#### Bin script
Finally, you can process jobs via the bin script that comes with Payload out of the box.
```sh
npx payload jobs:run --queue default --limit 10
```
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
```sh
npx payload jobs:run --cron "*/5 * * * *"
```

View File

@@ -1,120 +0,0 @@
---
title: Queues
label: Queues
order: 50
desc: A Queue is a specific group of jobs which can be executed in the order that they were added.
keywords: jobs queue, application framework, typescript, node, react, nextjs
---
Queues are the final aspect of Payload's Jobs Queue and deal with how to _run your jobs_. Up to this point, all we've covered is how to queue up jobs to run, but so far, we aren't actually running any jobs.
<Banner type="default">
A <strong>Queue</strong> is a grouping of jobs that should be executed in order of when they were added.
</Banner>
When you go to run jobs, Payload will query for any jobs that are added to the queue and then run them. By default, all queued jobs are added to the `default` queue.
**But, imagine if you wanted to have some jobs that run nightly, and other jobs which should run every five minutes.**
By specifying the `queue` name when you queue a new job using `payload.jobs.queue()`, you can queue certain jobs with `queue: 'nightly'`, and other jobs can be left as the default queue.
Then, you could configure two different runner strategies:
1. A `cron` that runs nightly, querying for jobs added to the `nightly` queue
2. Another that runs any jobs that were added to the `default` queue every ~5 minutes or so
## Executing jobs
As mentioned above, you can queue jobs, but the jobs won't run unless a worker picks up your jobs and runs them. This can be done in two ways:
#### Endpoint
You can execute jobs by making a fetch request to the `/api/payload-jobs/run` endpoint:
```ts
// Here, we're saying we want to run only 100 jobs for this invocation
// and we want to pull jobs from the `nightly` queue:
await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
});
```
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
**Vercel Cron Example**
If you're deploying on Vercel, you can add a `vercel.json` file in the root of your project that configures Vercel Cron to invoke the `run` endpoint on a cron schedule.
Here's an example of what this file will look like:
```json
{
"crons": [
{
"path": "/api/payload-jobs/run",
"schedule": "*/5 * * * *"
}
]
}
```
The configuration above schedules the endpoint `/api/payload-jobs/run` to be invoked every 5 minutes.
The last step will be to secure your `run` endpoint so that only the proper users can invoke the runner.
To do this, you can set an environment variable on your Vercel project called `CRON_SECRET`, which should be a random string—ideally 16 characters or longer.
Then, you can modify the `access` function for running jobs by ensuring that only Vercel can invoke your runner.
```ts
export default buildConfig({
// Other configurations...
jobs: {
access: {
run: ({ req }: { req: PayloadRequest }): boolean => {
// Allow logged in users to execute this endpoint (default)
if (req.user) return true
// If there is no logged in user, then check
// for the Vercel Cron secret to be present as an
// Authorization header:
const authHeader = req.headers.get('authorization');
return authHeader === `Bearer ${process.env.CRON_SECRET}`;
},
},
// Other job configurations...
}
})
```
This works because Vercel automatically makes the `CRON_SECRET` environment variable available to the endpoint as the `Authorization` header when triggered by the Vercel Cron, ensuring that the jobs can be run securely.
After the project is deployed to Vercel, the Vercel Cron job will automatically trigger the `/api/payload-jobs/run` endpoint in the specified schedule, running the queued jobs in the background.
#### Local API
If you want to process jobs programmatically from your server-side code, you can use the Local API:
```ts
const results = await payload.jobs.run()
// You can customize the queue name and limit by passing them as arguments:
await payload.jobs.run({ queue: 'nightly', limit: 100 })
```
#### Bin script
Finally, you can process jobs via the bin script that comes with Payload out of the box.
```sh
npx payload jobs:run --queue default --limit 10
```
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
```sh
npx payload jobs:run --cron "*/5 * * * *"
```

View File

@@ -1,141 +0,0 @@
---
title: Tasks
label: Tasks
order: 20
desc: A Task is a distinct function declaration that can be run within Payload's Jobs Queue.
keywords: jobs queue, application framework, typescript, node, react, nextjs
---
<Banner type="default">
A <strong>"Task"</strong> is a function definition that performs business logic and whose input and output are both strongly typed.
</Banner>
You can register Tasks on the Payload config, and then create [Jobs](/docs/beta/jobs-queue/jobs) or [Workflows](/docs/beta/jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried.
Tasks can either be defined within the `jobs.tasks` array in your payload config, or they can be defined inline within a workflow.
### Defining tasks in the config
Simply add a task to the `jobs.tasks` array in your Payload config. A task consists of the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this job. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the job. You can either pass a string-based path to the job function file, or the job function itself. If you are using large dependencies within your job, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this task. By default, this is "Task" + the capitalized task slug. |
| `outputSchema` | Define the output field schema - payload will generate a type for this schema. |
| `label` | Define a human-friendly label for this task. |
| `onFail` | Function to be executed if the task fails. |
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. |
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks picks up a Job that includes this task.
It should return an object with an `output` key, which should contain the output of the task as you've defined.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
{
// Configure this task to automatically retry
// up to two times
retries: 2,
// This is a unique identifier for the task
slug: 'createPost',
// These are the arguments that your Task will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
// These are the properties that the function should output
outputSchema: [
{
name: 'postID',
type: 'text',
required: true,
},
],
// This is the function that is run when the task is invoked
handler: async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
},
} as TaskConfig<'createPost'>,
]
}
})
```
In addition to defining handlers as functions directly provided to your Payload config, you can also pass an _absolute path_ to where the handler is defined. If your task has large dependencies, and you are planning on executing your jobs in a separate process that has access to the filesystem, this could be a handy way to make sure that your Payload + Next.js app remains quick to compile and has minimal dependencies.
In general, this is an advanced use case. Here's how this would look:
`payload.config.ts:`
```ts
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfig({
jobs: {
tasks: [
{
// ...
// The #createPostHandler is a named export within the `createPost.ts` file
handler: path.resolve(dirname, 'src/tasks/createPost.ts') + '#createPostHandler',
}
]
}
})
```
Then, the `createPost` file itself:
`src/tasks/createPost.ts:`
```ts
import type { TaskHandler } from 'payload'
export const createPostHandler: TaskHandler<'createPost'> = async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
req,
data: {
title: input.title,
},
})
return {
output: {
postID: newPost.id,
},
}
}
```

View File

@@ -1,150 +0,0 @@
---
title: Workflows
label: Workflows
order: 30
desc: A Task is a distinct function declaration that can be run within Payload's Jobs Queue.
keywords: jobs queue, application framework, typescript, node, react, nextjs
---
<Banner type="default">
A <strong>"Workflow"</strong> is an optional way to <em>combine multiple tasks together</em> in a way that can be gracefully retried from the point of failure.
</Banner>
They're most helpful when you have multiple tasks in a row, and you want to configure each task to be able to be retried if they fail.
If a task within a workflow fails, the Workflow will automatically "pick back up" on the task where it failed and **not re-execute any prior tasks that have already been executed**.
#### Defining a workflow
The most important aspect of a Workflow is the `handler`, where you can declare when and how the tasks should run by simply calling the `runTask` function. If any task within the workflow, fails, the entire `handler` function will re-run.
However, importantly, tasks that have successfully been completed will simply re-return the cached and saved output without running again. The Workflow will pick back up where it failed and only task from the failure point onward will be re-executed.
To define a JS-based workflow, simply add a workflow to the `jobs.wokflows` array in your Payload config. A workflow consists of the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this workflow. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the workflow. You can either pass a string-based path to the workflow function file, or workflow job function itself. If you are using large dependencies within your workflow, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this workflow. By default, this is "Workflow" + the capitalized workflow slug. |
| `label` | Define a human-friendly label for this workflow. |
| `queue` | Optionally, define the queue name that this workflow should be tied to. Defaults to "default". |
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
// The arguments that the workflow will accept
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
// The handler that defines the "control flow" of the workflow
// Notice how it uses the `tasks` argument to execute your predefined tasks.
// These are strongly typed!
handler: async ({ job, tasks }) => {
// This workflow first runs a task called `createPost`.
// You need to define a unique ID for this task invocation
// that will always be the same if this workflow fails
// and is re-executed in the future. Here, we hard-code it to '1'
const output = await tasks.createPost('1', {
input: {
title: job.input.title,
},
})
// Once the prior task completes, it will run a task
// called `updatePost`
await tasks.updatePost('2', {
input: {
post: job.taskStatus.createPost['1'].output.postID, // or output.postID
title: job.input.title + '2',
},
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```
#### Running tasks inline
In the above example, our workflow was executing tasks that we already had defined in our Payload config. But, you can also run tasks without predefining them.
To do this, you can use the `inlineTask` function.
The drawbacks of this approach are that tasks cannot be re-used across workflows as easily, and the **task data stored in the job** will not be typed. In the following example, the inline task data will be stored on the job under `job.taskStatus.inline['2']` but completely untyped, as types for dynamic tasks like these cannot be generated beforehand.
Example:
```ts
export default buildConfig({
// ...
jobs: {
tasks: [
// ...
]
workflows: [
{
slug: 'createPostAndUpdate',
inputSchema: [
{
name: 'title',
type: 'text',
required: true,
},
],
handler: async ({ job, tasks, inlineTask }) => {
// Here, we run a predefined task.
// The `createPost` handler arguments and return type
// are both strongly typed
const output = await tasks.createPost('1', {
input: {
title: job.input.title,
},
})
// Here, this task is not defined in the Payload config
// and is "inline". Its output will be stored on the Job in the database
// however its arguments will be untyped.
const { newPost } = await inlineTask('2', {
task: async ({ req }) => {
const newPost = await req.payload.update({
collection: 'post',
id: '2',
req,
retries: 3,
data: {
title: 'updated!',
},
})
return {
output: {
newPost
},
}
},
})
},
} as WorkflowConfig<'updatePost'>
]
}
})
```

View File

@@ -34,11 +34,11 @@ Then, render the `RefreshRouteOnSave` component anywhere in your `page.tsx`. Her
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
import { getPayload } from 'payload'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '../payload.config'
export default async function Page() {
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const page = await payload.findByID({
collection: 'pages',

View File

@@ -18,9 +18,12 @@ Payload can be used completely outside of Next.js which is helpful in cases like
Payload provides a convenient way to run standalone scripts, which can be useful for tasks like seeding your database or performing one-off operations.
In standalone scripts, you can simply import the Payload Config and use it right away. If you need an initialized copy of Payload, you can then use the `getPayload` function. This can be useful for tasks like seeding your database or performing other one-off operations.
In standalone scripts, can simply import the Payload Config and use it right away. If you need an initialized copy of Payload, you can then use the `getPayload` function. This can be useful for tasks like seeding your database or performing other one-off operations.
```ts
// We are importing `getPayload` because we don't need HMR
// for a standalone script. For usage of Payload inside Next.js,
// you should always use `import { getPayloadHMR } from '@payloadcms/next/utilities'` instead.
import { getPayload } from 'payload'
import config from '@payload-config'

View File

@@ -43,6 +43,27 @@ const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } })
If you want to import Payload in places where you don't have the option to access it from function arguments or `req`, you can import it and initialize it.
There are two places to import Payload:
**Option 1 - using HMR, within Next.js**
```ts
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '@payload-config'
const payload = await getPayloadHMR({ config })
```
You should import Payload using the first option (`getPayloadHMR`) if you are using Payload inside of Next.js (like route handlers, server components, and similar.)
This way, in Next.js development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload Config, your usage of Payload will always be in sync with your changes. In production, `getPayloadHMR` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
**Option 2 - outside of Next.js**
If you are using Payload outside of Next.js, for example in standalone scripts or in other frameworks, you can import Payload with no HMR functionality. Instead of using `getPayloadHMR`, you can use `getPayload`.
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
@@ -50,11 +71,7 @@ import config from '@payload-config'
const payload = await getPayload({ config })
```
If you're working in Next.js' development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload Config, your usage of Payload will always be in sync with your changes. In production, `getPayload` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
## Local options available
@@ -80,17 +97,6 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
_There are more options available on an operation by operation basis outlined below._
## Transactions
When your database uses transactions you need to thread req through to all local operations. Postgres uses transactions and MongoDB uses transactions when you are using replica sets. Passing req without transactions is still recommended.
```js
const post = await payload.find({
collection: 'posts',
req, // passing req is recommended
})
```
<Banner type="warning">
<strong>Note:</strong>
<br />

File diff suppressed because it is too large Load Diff

View File

@@ -37,23 +37,21 @@ _The exact query syntax will depend on the API you are using, but the concepts a
The following operators are available for use in queries:
| 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](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) |
| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) |
| 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](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
<Banner type="success">
<strong>Tip:</strong>
@@ -153,7 +151,7 @@ With the [REST API](../rest-api/overview), you can use the full power of Payload
To understand the syntax, you need to understand that complex URL search strings are parsed into a JSON object. This one isn't too bad, but more complex queries get unavoidably more difficult to write.
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs-esm`](https://www.npmjs.com/package/qs-esm) package to parse your JSON / object-formatted queries into query strings:
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs`](https://www.npmjs.com/package/qs) package to parse your JSON / object-formatted queries into query strings:
```ts
import { stringify } from 'qs-esm'

View File

@@ -6,13 +6,11 @@ desc: Payload select determines which fields are selected to the result.
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response, which can speed up the Payload API and reduce the amount of JSON that is sent to you from the API.
This is where Payload's `select` feature comes in. Here, you can define exactly which fields you'd like to retrieve from the API.
You may not need the full data from your Local API / REST queries, but only some specific fields. The select fields API can help you to optimize those cases.
## Local API
To specify `select` in the [Local API](../local-api/overview), you can use the `select` option in your query:
To specify select in the [Local API](../local-api/overview), you can use the `select` option in your query:
```ts
// Include mode
@@ -53,7 +51,7 @@ const getPosts = async () => {
<Banner type="warning">
<strong>Important:</strong>
To perform querying with `select` efficiently, Payload implements your `select` query on the database level. Because of that, your `beforeRead` and `afterRead` hooks may not receive the full `doc`.
To perform querying with `select` efficiently, it works on the database level. Because of that, your `beforeRead` and `afterRead` hooks may not receive the full `doc`.
</Banner>
@@ -69,7 +67,7 @@ fetch('https://localhost:3000/api/posts?select[color]=true&select[group][number]
To understand the syntax, you need to understand that complex URL search strings are parsed into a JSON object. This one isn't too bad, but more complex queries get unavoidably more difficult to write.
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs-esm`](https://www.npmjs.com/package/qs-esm) package to parse your JSON / object-formatted queries into query strings:
For this reason, we recommend to use the extremely helpful and ubiquitous [`qs`](https://www.npmjs.com/package/qs) package to parse your JSON / object-formatted queries into query strings:
```ts
import { stringify } from 'qs-esm'
@@ -102,17 +100,11 @@ const getPosts = async () => {
</Banner>
## defaultPopulate collection config property
## `defaultPopulate` collection config property
The `defaultPopulate` property allows you specify which fields to select when populating the collection from another document.
This is especially useful for links where only the `slug` is needed instead of the entire document.
With this feature, you can dramatically reduce the amount of JSON that is populated from [Relationship](/docs/beta/fields/relationship) or [Upload](/docs/beta/fields/upload) fields.
For example, in your content model, you might have a `Link` field which links out to a different page. When you go to retrieve these links, you really only need the `slug` of the page.
Loading all of the page content, its related links, and everything else is going to be overkill and will bog down your Payload APIs. Instead, you can define the `defaultPopulate` property on your `Pages` collection, so that when Payload "populates" a related Page, it only selects the `slug` field and therefore returns significantly less JSON:
```ts
import type { CollectionConfig } from 'payload'
@@ -137,20 +129,17 @@ export const Pages: CollectionConfig<'pages'> = {
}
```
## populate
## `populate`
Setting `defaultPopulate` will enforce that each time Payload performs a "population" of a related document, only the fields specified will be queried and returned. However, you can override `defaultPopulate` with the `populate` property in the Local and REST API:
**Local API:**
You can override `defaultPopulate` with the `populate` property in the Local and REST API
Local API:
```ts
const getPosts = async () => {
const posts = await payload.find({
collection: 'posts',
populate: {
// Select only `text` from populated docs in the "pages" collection
// Now, no matter what the `defaultPopulate` is set to on the "pages" collection,
// it will be overridden, and the `text` field will be returned instead.
pages: {
text: true,
}, // highlight-line
@@ -161,8 +150,7 @@ const getPosts = async () => {
}
```
**REST API:**
REST API:
```ts
fetch('https://localhost:3000/api/posts?populate[pages][text]=true') // highlight-line
.then((res) => res.json())

View File

@@ -140,23 +140,6 @@ export default buildConfig({
})
```
### Custom filename via hooks
You can customize the filename before it's uploaded to the server by using a `beforeOperation` hook.
```ts
beforeOperation: [
({ req, operation }) => {
if ((operation === 'create' || operation === 'update') && req.file) {
req.file.name = 'test.jpg'
}
},
],
```
The `req.file` object will have additional information about the file, such as mimeType and extension, and you also have full access to the file data itself.
The filename from here will also be threaded to image sizes if they're enabled.
## Image Sizes
If you specify an array of `imageSizes` to your `upload` config, Payload will automatically crop and resize your uploads to fit each of the sizes specified by your config.

View File

@@ -14,7 +14,7 @@ Payload offers additional storage adapters to handle file uploads. These adapter
| AWS S3 | [`@payloadcms/storage-s3`](https://github.com/payloadcms/payload/tree/beta/packages/storage-s3) |
| Azure | [`@payloadcms/storage-azure`](https://github.com/payloadcms/payload/tree/beta/packages/storage-azure) |
| Google Cloud Storage | [`@payloadcms/storage-gcs`](https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs) |
| Uploadthing | [`@payloadcms/storage-uploadthing`](https://github.com/payloadcms/payload/tree/beta/packages/uploadthing) |
## Vercel Blob Storage
[`@payloadcms/storage-vercel-blob`](https://www.npmjs.com/package/@payloadcms/storage-vercel-blob)
@@ -43,8 +43,8 @@ export default buildConfig({
enabled: true, // Optional, defaults to true
// Specify which collections should use Vercel Blob
collections: {
media: true,
'media-with-prefix': {
[Media.slug]: true,
[MediaWithPrefix.slug]: {
prefix: 'my-prefix',
},
},
@@ -90,8 +90,8 @@ export default buildConfig({
plugins: [
s3Storage({
collections: {
media: true,
'media-with-prefix': {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
prefix,
},
},
@@ -137,8 +137,8 @@ export default buildConfig({
plugins: [
azureStorage({
collections: {
media: true,
'media-with-prefix': {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
prefix,
},
},
@@ -186,8 +186,8 @@ export default buildConfig({
plugins: [
gcsStorage({
collections: {
media: true,
'media-with-prefix': {
[mediaSlug]: true,
[mediaWithPrefixSlug]: {
prefix,
},
},
@@ -224,7 +224,7 @@ pnpm add @payloadcms/storage-uploadthing@beta
### Usage
- Configure the `collections` object to specify which collections should use uploadthing. The slug _must_ match one of your existing collection slugs and be an `upload` type.
- Get a token from Uploadthing and set it as `token` in the `options` object.
- Get an API key from Uploadthing and set it as `apiKey` in the `options` object.
- `acl` is optional and defaults to `public-read`.
```ts
@@ -233,10 +233,10 @@ export default buildConfig({
plugins: [
uploadthingStorage({
collections: {
media: true,
[mediaSlug]: true,
},
options: {
token: process.env.UPLOADTHING_TOKEN,
apiKey: process.env.UPLOADTHING_SECRET,
acl: 'public-read',
},
}),
@@ -248,7 +248,7 @@ export default buildConfig({
| Option | Description | Default |
| ---------------- | ----------------------------------------------- | ------------- |
| `token` | Token from Uploadthing. Required. | |
| `apiKey` | API key from Uploadthing. Required. | |
| `acl` | Access control list for files that are uploaded | `public-read` |
| `logLevel` | Log level for Uploadthing | `info` |
| `fetch` | Custom fetch function | `fetch` |

View File

@@ -48,12 +48,12 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
```ts
import { headers as getHeaders } from 'next/headers.js'
import { getPayload } from 'payload'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '../../payload.config'
export default async function AccountPage({ searchParams }) {
const headers = getHeaders()
const payload = await getPayload({ config: configPromise })
const payload = await getPayloadHMR({ config: configPromise })
const { permissions, user } = await payload.auth({ headers })
if (!user) {

View File

@@ -1,7 +1,7 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import Link from 'next/link'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React, { Fragment } from 'react'
import config from '../../../payload.config'
@@ -14,7 +14,7 @@ import classes from './index.module.scss'
export default async function Account() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { permissions, user } = await payload.auth({ headers })
if (!user) {

View File

@@ -1,6 +1,6 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../payload.config'
@@ -11,7 +11,7 @@ import classes from './index.module.scss'
export default async function CreateAccount() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { user } = await payload.auth({ headers })
if (user) {

View File

@@ -1,17 +1,17 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import { RenderParams } from '../_components/RenderParams'
import classes from './index.module.scss'
import { LoginForm } from './LoginForm'
import classes from './index.module.scss'
export default async function Login() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { user } = await payload.auth({ headers })
if (user) {

View File

@@ -1,16 +1,16 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import Link from 'next/link'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import classes from './index.module.scss'
import { LogoutPage } from './LogoutPage'
import classes from './index.module.scss'
export default async function Logout() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { user } = await payload.auth({ headers })
if (!user) {

View File

@@ -1,6 +1,6 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import Link from 'next/link'
import { getPayload } from 'payload'
import React, { Fragment } from 'react'
import config from '../../payload.config'
@@ -9,7 +9,7 @@ import { HydrateClientUser } from './_components/HydrateClientUser'
export default async function HomePage() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { permissions, user } = await payload.auth({ headers })
return (

View File

@@ -1,16 +1,16 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import classes from './index.module.scss'
import { RecoverPasswordForm } from './RecoverPasswordForm'
import classes from './index.module.scss'
export default async function RecoverPassword() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { user } = await payload.auth({ headers })
if (user) {

View File

@@ -1,16 +1,16 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers.js'
import { redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import classes from './index.module.scss'
import { ResetPasswordForm } from './ResetPasswordForm'
import classes from './index.module.scss'
export default async function ResetPassword() {
const headers = getHeaders()
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const { user } = await payload.auth({ headers })
if (user) {

View File

@@ -1,5 +1,4 @@
DATABASE_URI=mongodb://127.0.0.1/payload-example-email
MONGODB_URI=mongodb://127.0.0.1/payload-example-email
PAYLOAD_SECRET=
NODE_ENV=development
PAYLOAD_SECRET=PAYLOAD_EMAIL_EXAMPLE_SECRET_KEY
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000

View File

@@ -1,4 +1,7 @@
module.exports = {
root: true,
extends: ['@payloadcms'],
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
},
}

View File

@@ -1,24 +0,0 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"sourceMaps": true,
"jsc": {
"target": "esnext",
"parser": {
"syntax": "typescript",
"tsx": true,
"dts": true
},
"transform": {
"react": {
"runtime": "automatic",
"pragmaFrag": "React.Fragment",
"throwIfNamespace": true,
"development": false,
"useBuiltins": true
}
}
},
"module": {
"type": "es6"
}
}

View File

@@ -7,31 +7,30 @@ This example demonstrates how to integrate email functionality into Payload.
To spin up this example locally, follow these steps:
1. Clone this repo
2. `cp .env.example .env` to copy the example environment variables
3. `pnpm install && pnpm dev` to install dependencies and start the dev server
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 and seed the database
5. open `http://localhost:3000/admin` to access the admin panel
5. `open http://localhost:8000/admin` to access the admin panel
6. Create your first user
## How it works
Email functionality in Payload is configured using adapters. The recommended adapter for most use cases is the [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) package.
Payload utilizes [NodeMailer](https://nodemailer.com/about/) for email functionality. Once you add your email configuration to `payload.init()`, you send email from anywhere in your application just by calling `payload.sendEmail({})`.
To enable email, pass your adapter configuration to the `email` property in the Payload Config. This allows Payload to send auth-related emails for password resets, new user verifications, and other email needs.
1. In the Payload Config file, add your email adapter to the `email` property. For example, the `@payloadcms/email-nodemailer` adapter can be configured for SMTP, SendGrid, or other supported transports. During development, if no configuration is provided, Payload will use a mock service via [ethereal.email](ethereal.email).
1. Navigate to `src/server.ts` - this is where your email config gets passed to Payload
2. Open `src/email/transport.ts` - here we are defining the email config. You can use an env variable to switch between the mock email transport and live email service.
Now we can start sending email!
2. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
3. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
Let's not forget our authentication emails...
3. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
4. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
Speaking of customization...
4. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
5. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
That's all you need, now you can go ahead and test out this repo by creating a new `user` or `newsletter-signup` and see the email integration in action.
@@ -41,10 +40,10 @@ To spin up this example locally, follow the [Quick Start](#quick-start).
## Production
To run Payload in production, you need to build and start the Admin panel. To do so, follow these steps:
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
1. Invoke the `next build` script by running `pnpm build` or `npm run build` in your project root. This creates a `.next` directory with a production-ready admin bundle.
1. Finally run `pnpm start` or `npm run start` to run Node in production and serve Payload from the `.build` directory.
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
### Deployment

View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@@ -0,0 +1,5 @@
{
"ext": "ts",
"exec": "ts-node src/server.ts -- -I",
"stdin": false
}

View File

@@ -1,57 +1,35 @@
{
"name": "payload-example-email",
"version": "1.0.0",
"description": "Payload Email integration example.",
"version": "1.0.0",
"main": "dist/server.js",
"license": "MIT",
"type": "module",
"scripts": {
"_dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true NODE_OPTIONS=--no-deprecation next dev",
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
"generate:schema": "payload-graphql generate:schema",
"generate:types": "payload generate:types",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
"build:server": "tsc",
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
"lint": "eslint src",
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"dependencies": {
"@payloadcms/db-mongodb": "beta",
"@payloadcms/email-nodemailer": "beta",
"@payloadcms/next": "beta",
"@payloadcms/richtext-lexical": "beta",
"@payloadcms/ui": "beta",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"ejs": "3.1.10",
"graphql": "^16.9.0",
"juice": "11.0.0",
"next": "15.0.0",
"payload": "beta",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020"
"express": "^4.17.1",
"payload": "latest",
"handlebars": "^4.7.7",
"inline-css": "^4.0.2"
},
"devDependencies": {
"@payloadcms/graphql": "beta",
"@swc/core": "^1.6.13",
"@types/ejs": "^3.1.5",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"eslint": "^8.57.0",
"eslint-config-next": "15.0.0",
"tsx": "^4.16.2",
"typescript": "5.5.2"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"
},
"pnpm": {
"overrides": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}
},
"overrides": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
"@types/express": "^4.17.9",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"eslint": "^8.19.0",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next'
import config from '@payload-config'
import { generatePageMetadata, NotFoundPage } from '@payloadcms/next/views'
import { importMap } from '../importMap'
type Args = {
params: Promise<{
segments: string[]
}>
searchParams: Promise<{
[key: string]: string | string[]
}>
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, importMap, params, searchParams })
export default NotFound

View File

@@ -1,25 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import type { Metadata } from 'next'
import config from '@payload-config'
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
import { importMap } from '../importMap'
type Args = {
params: Promise<{
segments: string[]
}>
searchParams: Promise<{
[key: string]: string | string[]
}>
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) =>
RootPage({ config, importMap, params, searchParams })
export default Page

View File

@@ -1 +0,0 @@
export const importMap = {}

View File

@@ -1,10 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config'
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const OPTIONS = REST_OPTIONS(config)

View File

@@ -1,6 +0,0 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)

View File

@@ -1,32 +0,0 @@
import type { ServerFunctionClient } from 'payload'
import '@payloadcms/next/css'
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import config from '@payload-config'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react'
import { importMap } from './admin/importMap.js'
import './custom.scss'
type Args = {
children: React.ReactNode
}
const serverFunction: ServerFunctionClient = async function (args) {
'use server'
return handleServerFunctions({
...args,
config,
importMap,
})
}
const Layout = ({ children }: Args) => (
<RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
{children}
</RootLayout>
)
export default Layout

View File

@@ -1,12 +1,28 @@
import type { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload/types'
import generateEmailHTML from '../email/generateEmailHTML'
import { generateEmailHTML } from '../email/generateEmailHTML'
export const Newsletter: CollectionConfig = {
const Newsletter: CollectionConfig = {
slug: 'newsletter-signups',
admin: {
defaultColumns: ['name', 'email'],
},
hooks: {
afterChange: [
async ({ doc, operation, req }) => {
if (operation === 'create') {
req.payload.sendEmail({
to: doc.email,
from: 'sender@example.com',
subject: 'Thanks for signing up!',
html: await generateEmailHTML({
headline: 'Welcome to the newsletter!',
content: `<p>${doc.name ? `Hi ${doc.name}!` : 'Hi!'} We'll be in touch soon...</p>`,
}),
})
}
},
],
},
fields: [
{
name: 'name',
@@ -18,25 +34,6 @@ export const Newsletter: CollectionConfig = {
required: true,
},
],
hooks: {
afterChange: [
async ({ doc, operation, req }) => {
if (operation === 'create') {
req.payload
.sendEmail({
from: 'sender@example.com',
html: await generateEmailHTML({
content: `<p>${doc.name ? `Hi ${doc.name}!` : 'Hi!'} We'll be in touch soon...</p>`,
headline: 'Welcome to the newsletter!',
}),
subject: 'Thanks for signing up!',
to: doc.email,
})
.catch((error) => {
console.error('Error sending email:', error)
})
}
},
],
},
}
export default Newsletter

View File

@@ -1,23 +1,23 @@
import type { CollectionConfig } from 'payload'
import type { CollectionConfig } from 'payload/types'
import { generateForgotPasswordEmail } from '../email/generateForgotPasswordEmail'
import { generateVerificationEmail } from '../email/generateVerificationEmail'
import generateForgotPasswordEmail from '../email/generateForgotPasswordEmail'
import generateVerificationEmail from '../email/generateVerificationEmail'
export const Users: CollectionConfig = {
const Users: CollectionConfig = {
slug: 'users',
auth: {
verify: {
generateEmailSubject: () => 'Verify your email',
generateEmailHTML: generateVerificationEmail,
},
forgotPassword: {
generateEmailSubject: () => 'Reset your password',
generateEmailHTML: generateForgotPasswordEmail,
},
},
admin: {
useAsTitle: 'email',
},
auth: {
forgotPassword: {
generateEmailHTML: generateForgotPasswordEmail,
generateEmailSubject: () => 'Reset your password',
},
verify: {
generateEmailHTML: generateVerificationEmail,
generateEmailSubject: () => 'Verify your email',
},
},
fields: [
{
name: 'name',
@@ -25,3 +25,5 @@ export const Users: CollectionConfig = {
},
],
}
export default Users

View File

@@ -1,17 +1,24 @@
import ejs from 'ejs'
import fs from 'fs'
import juice from 'juice'
import Handlebars from 'handlebars'
import inlineCSS from 'inline-css'
import path from 'path'
export const generateEmailHTML = async (data: any): Promise<string> => {
const templatePath = path.join(process.cwd(), 'src/email/template.ejs')
const templateContent = fs.readFileSync(templatePath, 'utf8')
const template = fs.readFileSync
? fs.readFileSync(path.join(__dirname, './template.html'), 'utf8')
: ''
// Compile and render the template with EJS
const preInlinedCSS = ejs.render(templateContent, { ...data, cta: data.cta || {} })
// Compile the template
const getHTML = Handlebars.compile(template)
// Inline CSS
const html = juice(preInlinedCSS)
const generateEmailHTML = async (data): Promise<string> => {
const preInlinedCSS = getHTML(data)
return Promise.resolve(html)
const html = await inlineCSS(preInlinedCSS, {
url: ' ',
removeStyleTags: false,
})
return html
}
export default generateEmailHTML

View File

@@ -1,24 +1,13 @@
import type { PayloadRequest } from 'payload'
import generateEmailHTML from './generateEmailHTML'
import { generateEmailHTML } from './generateEmailHTML'
type ForgotPasswordEmailArgs =
| {
req?: PayloadRequest
token?: string
user?: any
}
| undefined
export const generateForgotPasswordEmail = async (
args: ForgotPasswordEmailArgs,
): Promise<string> => {
return generateEmailHTML({
const generateForgotPasswordEmail = async ({ token }): Promise<string> =>
generateEmailHTML({
headline: 'Locked out?',
content: '<p>Let&apos;s get you back in.</p>',
cta: {
buttonLabel: 'Reset your password',
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/reset-password?token=${args?.token}`,
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/reset-password?token=${token}`,
},
headline: 'Locked out?',
})
}
export default generateForgotPasswordEmail

View File

@@ -1,26 +1,16 @@
import { generateEmailHTML } from './generateEmailHTML'
import generateEmailHTML from './generateEmailHTML'
type User = {
email: string
name?: string
}
type GenerateVerificationEmailArgs = {
token: string
user: User
}
export const generateVerificationEmail = async (
args: GenerateVerificationEmailArgs,
): Promise<string> => {
const { token, user } = args
const generateVerificationEmail = async (args): Promise<string> => {
const { user, token } = args
return generateEmailHTML({
headline: 'Verify your account',
content: `<p>Hi${user.name ? ' ' + user.name : ''}! Validate your account by clicking the button below.</p>`,
cta: {
buttonLabel: 'Verify',
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/verify?token=${token}&email=${user.email}`,
},
headline: 'Verify your account',
})
}
export default generateVerificationEmail

View File

@@ -1,327 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style type="text/css">
body,
html {
margin: 0;
padding: 0;
}
body,
html,
.bg {
height: 100%;
}
body,
h1,
h2,
h3,
h4,
p,
em,
strong {
font-family: sans-serif;
}
body {
font-size: 15px;
color: #333333;
}
a {
color: #333333;
outline: 0;
text-decoration: underline;
}
a img {
border: 0;
outline: 0;
}
img {
max-width: 100%;
height: auto;
vertical-align: top;
}
h1,
h2,
h3,
h4,
h5 {
font-weight: 900;
line-height: 1.25;
}
h1 {
font-size: 40px;
color: #333333;
margin: 0 0 25px 0;
}
h2 {
color: #333333;
margin: 0 0 25px 0;
font-size: 30px;
line-height: 30px;
}
h3 {
font-size: 25px;
color: #333333;
margin: 0 0 25px 0;
}
h4 {
font-size: 20px;
color: #333333;
margin: 0 0 15px 0;
line-height: 30px;
}
h5 {
color: #333333;
font-size: 17px;
font-weight: 900;
margin: 0 0 15px;
}
table {
border-collapse: collapse;
}
p,
td {
font-size: 14px;
line-height: 25px;
color: #333333;
}
p {
margin: 0 0 25px;
}
ul {
padding-left: 15px;
margin-left: 15px;
font-size: 14px;
line-height: 25px;
margin-bottom: 25px;
}
li {
font-size: 14px;
line-height: 25px;
color: #333333;
}
table.hr td {
font-size: 0;
line-height: 2px;
}
.white {
color: white;
}
/********************************
MAIN
********************************/
.main {
background: white;
}
/********************************
MAX WIDTHS
********************************/
.max-width {
max-width: 800px;
width: 94%;
margin: 0 3%;
}
/********************************
REUSABLES
********************************/
.padding {
padding: 60px;
}
.center {
text-align: center;
}
.no-border {
border: 0;
outline: none;
text-decoration: none;
}
.no-margin {
margin: 0;
}
.spacer {
line-height: 45px;
height: 45px;
}
/********************************
PANELS
********************************/
.panel {
width: 100%;
}
@media screen and (max-width: 800px) {
h1 {
font-size: 24px !important;
margin: 0 0 20px 0 !important;
}
h2 {
font-size: 20px !important;
margin: 0 0 20px 0 !important;
}
h3 {
font-size: 20px !important;
margin: 0 0 20px 0 !important;
}
h4 {
font-size: 18px !important;
margin: 0 0 15px 0 !important;
}
h5 {
font-size: 15px !important;
margin: 0 0 10px !important;
}
.max-width {
width: 90% !important;
margin: 0 5% !important;
}
td.padding {
padding: 30px !important;
}
td.padding-vert {
padding-top: 20px !important;
padding-bottom: 20px !important;
}
td.padding-horiz {
padding-left: 20px !important;
padding-right: 20px !important;
}
.spacer {
line-height: 20px !important;
height: 20px !important;
}
}
</style>
</head>
<body>
<div style="background-color: #f3f3f3; height: 100%">
<table height="100%" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f3f3f3">
<tr>
<td valign="top" align="left">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td align="center" valign="top">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td align="center">
<table class="max-width" cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td class="spacer">&nbsp;</td>
</tr>
<tr>
<td class="padding main">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td>
<!-- LOGO -->
<a href="https://payloadcms.com/" target="_blank">
<img src="https://payloadcms.com/images/logo-dark.png" width="150"
height="auto" />
</a>
</td>
</tr>
<tr>
<td class="spacer">&nbsp;</td>
</tr>
<tr>
<td>
<!-- HEADLINE -->
<h1 style="margin: 0 0 30px"><%= headline %></h1>
</td>
</tr>
<tr>
<td>
<!-- CONTENT -->
<%- content %>
<!-- CTA -->
<% if (cta) { %>
<div>
<a href="<%= cta.url %>" style="
background-color: #222222;
border-radius: 4px;
color: #ffffff;
display: inline-block;
font-family: sans-serif;
font-size: 13px;
font-weight: bold;
line-height: 60px;
text-align: center;
text-decoration: none;
width: 200px;
-webkit-text-size-adjust: none;
">
<%= cta.buttonLabel %>
</a>
</div>
<% } %>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,345 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style type="text/css">
body,
html {
margin: 0;
padding: 0;
}
body,
html,
.bg {
height: 100%;
}
body,
h1,
h2,
h3,
h4,
p,
em,
strong {
font-family: sans-serif;
}
body {
font-size: 15px;
color: #333333;
}
a {
color: #333333;
outline: 0;
text-decoration: underline;
}
a img {
border: 0;
outline: 0;
}
img {
max-width: 100%;
height: auto;
vertical-align: top;
}
h1,
h2,
h3,
h4,
h5 {
font-weight: 900;
line-height: 1.25;
}
h1 {
font-size: 40px;
color: #333333;
margin: 0 0 25px 0;
}
h2 {
color: #333333;
margin: 0 0 25px 0;
font-size: 30px;
line-height: 30px;
}
h3 {
font-size: 25px;
color: #333333;
margin: 0 0 25px 0;
}
h4 {
font-size: 20px;
color: #333333;
margin: 0 0 15px 0;
line-height: 30px;
}
h5 {
color: #333333;
font-size: 17px;
font-weight: 900;
margin: 0 0 15px;
}
table {
border-collapse: collapse;
}
p,
td {
font-size: 14px;
line-height: 25px;
color: #333333;
}
p {
margin: 0 0 25px;
}
ul {
padding-left: 15px;
margin-left: 15px;
font-size: 14px;
line-height: 25px;
margin-bottom: 25px;
}
li {
font-size: 14px;
line-height: 25px;
color: #333333;
}
table.hr td {
font-size: 0;
line-height: 2px;
}
.white {
color: white;
}
/********************************
MAIN
********************************/
.main {
background: white;
}
/********************************
MAX WIDTHS
********************************/
.max-width {
max-width: 800px;
width: 94%;
margin: 0 3%;
}
/********************************
REUSABLES
********************************/
.padding {
padding: 60px;
}
.center {
text-align: center;
}
.no-border {
border: 0;
outline: none;
text-decoration: none;
}
.no-margin {
margin: 0;
}
.spacer {
line-height: 45px;
height: 45px;
}
/********************************
PANELS
********************************/
.panel {
width: 100%;
}
@media screen and (max-width: 800px) {
h1 {
font-size: 24px !important;
margin: 0 0 20px 0 !important;
}
h2 {
font-size: 20px !important;
margin: 0 0 20px 0 !important;
}
h3 {
font-size: 20px !important;
margin: 0 0 20px 0 !important;
}
h4 {
font-size: 18px !important;
margin: 0 0 15px 0 !important;
}
h5 {
font-size: 15px !important;
margin: 0 0 10px !important;
}
.max-width {
width: 90% !important;
margin: 0 5% !important;
}
td.padding {
padding: 30px !important;
}
td.padding-vert {
padding-top: 20px !important;
padding-bottom: 20px !important;
}
td.padding-horiz {
padding-left: 20px !important;
padding-right: 20px !important;
}
.spacer {
line-height: 20px !important;
height: 20px !important;
}
}
</style>
</head>
<body>
<div style="background-color: #f3f3f3; height: 100%">
<table
height="100%"
width="100%"
cellpadding="0"
cellspacing="0"
border="0"
bgcolor="#f3f3f3"
style="background-color: #f3f3f3"
>
<tr>
<td valign="top" align="left">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td align="center" valign="top">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td align="center">
<table
class="max-width"
cellpadding="0"
cellspacing="0"
border="0"
width="100%"
style="width: 100%"
>
<tbody>
<tr>
<td class="spacer">&nbsp;</td>
</tr>
<tr>
<td class="padding main">
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td>
<!-- LOGO -->
<a href="https://payloadcms.com/" target="_blank">
<img
src="https://payloadcms.com/images/logo-dark.png"
width="150"
height="auto"
/>
</a>
</td>
</tr>
<tr>
<td class="spacer">&nbsp;</td>
</tr>
<tr>
<td>
<!-- HEADLINE -->
<h1 style="margin: 0 0 30px">{{headline}}</h1>
</td>
</tr>
<tr>
<td>
<!-- CONTENT -->
{{{content}}}
<!-- CTA -->
{{#if cta}}
<div>
<a
href="{{cta.url}}"
style="
background-color: #222222;
border-radius: 4px;
color: #ffffff;
display: inline-block;
font-family: sans-serif;
font-size: 13px;
font-weight: bold;
line-height: 60px;
text-align: center;
text-decoration: none;
width: 200px;
-webkit-text-size-adjust: none;
"
>
{{cta.buttonLabel}}
</a>
</div>
{{/if}}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
let email
if (process.env.NODE_ENV === 'production') {
email = {
fromName: 'Payload',
fromAddress: 'info@payloadcms.com',
transportOptions: {
// Configure a custom transport here
},
}
} else {
email = {
fromName: 'Ethereal Email',
fromAddress: 'example@ethereal.com',
logMockCredentials: true,
}
}
export default email

View File

@@ -0,0 +1 @@
export default {}

View File

@@ -1,5 +1,4 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
@@ -7,213 +6,30 @@
*/
export interface Config {
auth: {
users: UserAuthOperations;
};
collections: {
'newsletter-signups': NewsletterSignup;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
'newsletter-signups': NewsletterSignupsSelect<false> | NewsletterSignupsSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: null;
user: User & {
collection: 'users';
};
jobs?: {
tasks: unknown;
workflows?: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "newsletter-signups".
*/
export interface NewsletterSignup {
id: string;
name?: string | null;
name?: string;
email: string;
updatedAt: string;
createdAt: string;
updatedAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
name?: string | null;
updatedAt: string;
name?: string;
email?: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
_verified?: boolean;
_verificationToken?: string;
loginAttempts?: number;
lockUntil?: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
_verified?: boolean | null;
_verificationToken?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'newsletter-signups';
value: string | NewsletterSignup;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
password?: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "newsletter-signups_select".
*/
export interface NewsletterSignupsSelect<T extends boolean = true> {
name?: T;
email?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
name?: T;
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
_verified?: T;
_verificationToken?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
export interface GeneratedTypes extends Config {}
}

View File

@@ -1,37 +1,42 @@
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import dotenv from 'dotenv'
import path from 'path'
import { buildConfig } from 'payload'
import { fileURLToPath } from 'url'
import { buildConfig } from 'payload/config'
import { Newsletter } from './collections/Newsletter'
import { Users } from './collections/Users'
import Users from './collections/Users'
import Newsletter from './collections/Newsletter'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
dotenv.config({
path: path.resolve(__dirname, '../.env'),
})
const mockModulePath = path.resolve(__dirname, './emptyModule.js')
// eslint-disable-next-line no-restricted-exports
export default buildConfig({
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
user: Users.slug,
webpack: (config) => ({
...config,
resolve: {
...config?.resolve,
alias: [
'fs',
'handlebars',
'inline-css',
path.resolve(__dirname, './email/transport'),
path.resolve(__dirname, './email/generateEmailHTML'),
path.resolve(__dirname, './email/generateForgotPasswordEmail'),
path.resolve(__dirname, './email/generateVerificationEmail'),
].reduce(
(aliases, importPath) => ({
...aliases,
[importPath]: mockModulePath,
}),
config.resolve.alias,
),
},
}),
},
collections: [Newsletter, Users],
db: mongooseAdapter({
url: process.env.DATABASE_URI || '',
}),
editor: lexicalEditor({}),
// For example use case, we are passing nothing to nodemailerAdapter
// This will default to using etherial.email
email: nodemailerAdapter(),
graphQL: {
schemaOutputFile: path.resolve(dirname, 'generated-schema.graphql'),
},
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
})

View File

@@ -0,0 +1,30 @@
import express from 'express'
import path from 'path'
import payload from 'payload'
import email from './email/transport'
require('dotenv').config({
path: path.resolve(__dirname, '../.env'),
})
const app = express()
app.get('/', (_, res) => {
res.redirect('/admin')
})
const start = async (): Promise<void> => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
email,
onInit: () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
},
})
app.listen(8000)
}
start()

View File

@@ -1,48 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"strict": false,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
],
"@payload-config": [
"src/payload.config.ts"
],
"@payload-types": [
"src/payload-types.ts"
]
},
"target": "ES2017"
"payload/generated-types": ["./src/payload-types.ts"],
"node_modules/*": ["./node_modules/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"src/mocks/emptyObject.js"
],
"exclude": [
"node_modules"
]
"include": ["src"],
"exclude": ["node_modules", "dist", "build"],
"ts-node": {
"transpileOnly": true
}
}

7942
examples/email/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -107,11 +107,11 @@ Then, render `RefreshRouteOnSave` anywhere in your `page.tsx`. Here's an example
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
import { getPayload } from 'payload'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import config from '../payload.config'
export default async function Page() {
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const page = await payload.find({
collection: 'pages',

View File

@@ -1,22 +1,23 @@
import { notFound } from 'next/navigation'
/* eslint-disable no-restricted-exports */
import { getPayload } from 'payload'
import React, { Fragment } from 'react'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { notFound } from 'next/navigation'
import React from 'react'
import { Fragment } from 'react'
import type { Page as PageType } from '../../../payload-types'
import config from '../../../payload.config'
import { Gutter } from '../_components/Gutter'
import RichText from '../_components/RichText'
import classes from './index.module.scss'
import { RefreshRouteOnSave } from './RefreshRouteOnSave'
import classes from './index.module.scss'
interface PageParams {
params: { slug: string }
}
export default async function Page({ params: { slug = 'home' } }: PageParams) {
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const pageRes = await payload.find({
collection: 'pages',
@@ -29,7 +30,7 @@ export default async function Page({ params: { slug = 'home' } }: PageParams) {
},
})
const data = pageRes?.docs?.[0] as null | PageType
const data = pageRes?.docs?.[0] as PageType | null
if (data === null) {
return notFound()
@@ -48,7 +49,7 @@ export default async function Page({ params: { slug = 'home' } }: PageParams) {
}
export async function generateStaticParams() {
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const pagesRes = await payload.find({
collection: 'pages',
@@ -65,5 +66,5 @@ export async function generateStaticParams() {
slug,
}
: {},
)
) // eslint-disable-line function-paren-newline
}

View File

@@ -1,6 +1,6 @@
import { getPayloadHMR } from '@payloadcms/next/utilities'
import Image from 'next/image'
import Link from 'next/link'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../../../payload.config'
@@ -9,7 +9,7 @@ import { Gutter } from '../Gutter'
import classes from './index.module.scss'
export const Header = async () => {
const payload = await getPayload({ config })
const payload = await getPayloadHMR({ config })
const mainMenu = await payload.findGlobal({
slug: 'main-menu',

View File

@@ -1,16 +1,16 @@
import type { Where } from 'payload'
import configPromise from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { headers as getHeaders } from 'next/headers'
import { notFound, redirect } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import { RenderPage } from '../../../components/RenderPage'
export default async function Page({ params }: { params: { slug?: string[]; tenant: string } }) {
const headers = await getHeaders()
const payload = await getPayload({ config: configPromise })
const payload = await getPayloadHMR({ config: configPromise })
const { user } = await payload.auth({ headers })
const tenantsQuery = await payload.find({

View File

@@ -20,9 +20,7 @@ export const Login = ({ tenantSlug }: Props) => {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (!usernameRef?.current?.value || !passwordRef?.current?.value) {
return
}
if (!usernameRef?.current?.value || !passwordRef?.current?.value) {return}
const actionRes = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_URL}/api/users/external-users/login`,
{

View File

@@ -6,9 +6,7 @@ import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
export const ensureUniqueSlug: FieldHook = async ({ data, originalDoc, req, value }) => {
// if value is unchanged, skip validation
if (originalDoc.slug === value) {
return value
}
if (originalDoc.slug === value) {return value}
const incomingTenantID = typeof data?.tenant === 'object' ? data.tenant.id : data?.tenant
const currentTenantID =

View File

@@ -14,9 +14,7 @@ export const Pages: CollectionConfig = {
read: (args) => {
// when viewing pages inside the admin panel
// restrict access to the ones your user has access to
if (isPayloadAdminPanel(args.req)) {
return filterByTenantRead(args)
}
if (isPayloadAdminPanel(args.req)) {return filterByTenantRead(args)}
// when viewing pages from outside the admin panel
// you should be able to see your tenants and public tenants

View File

@@ -7,9 +7,7 @@ export const tenantRead: Access = (args) => {
const req = args.req
// Super admin can read all
if (isSuperAdmin(args)) {
return true
}
if (isSuperAdmin(args)) {return true}
const tenantIDs = getTenantAccessIDs(req.user)

View File

@@ -7,19 +7,13 @@ import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAcces
export const createAccess: Access<User> = (args) => {
const { req } = args
if (!req.user) {
return false
}
if (!req.user) {return false}
if (isSuperAdmin(args)) {
return true
}
if (isSuperAdmin(args)) {return true}
const adminTenantAccessIDs = getTenantAdminTenantAccessIDs(req.user)
if (adminTenantAccessIDs.length > 0) {
return true
}
if (adminTenantAccessIDs.length > 0) {return true}
return false
}

View File

@@ -1,8 +1,6 @@
import type { Access } from 'payload'
export const isAccessingSelf: Access = ({ id, req }) => {
if (!req?.user) {
return false
}
if (!req?.user) {return false}
return req.user.id === id
}

View File

@@ -5,13 +5,9 @@ import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAcces
export const updateAndDeleteAccess: Access = (args) => {
const { req } = args
if (!req.user) {
return false
}
if (!req.user) {return false}
if (isSuperAdmin(args)) {
return true
}
if (isSuperAdmin(args)) {return true}
const adminTenantAccessIDs = getTenantAdminTenantAccessIDs(req.user)

View File

@@ -6,9 +6,7 @@ import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
export const ensureUniqueUsername: FieldHook = async ({ data, originalDoc, req, value }) => {
// if value is unchanged, skip validation
if (originalDoc.username === value) {
return value
}
if (originalDoc.username === value) {return value}
const incomingTenantID = typeof data?.tenant === 'object' ? data.tenant.id : data?.tenant
const currentTenantID =

View File

@@ -8,9 +8,7 @@ export const autofillTenant: FieldHook = ({ req, value }) => {
// return that tenant ID as the value
if (!value) {
const tenantIDs = getTenantAccessIDs(req.user)
if (tenantIDs.length === 1) {
return tenantIDs[0]
}
if (tenantIDs.length === 1) {return tenantIDs[0]}
}
return value

View File

@@ -10,9 +10,7 @@ export const tenantField: Field = {
access: {
read: () => true,
update: (args) => {
if (isSuperAdmin(args)) {
return true
}
if (isSuperAdmin(args)) {return true}
return tenantFieldUpdate(args)
},
},

View File

@@ -1,9 +1,7 @@
import type { User } from '../payload-types'
export const getTenantAccessIDs = (user: null | User): string[] => {
if (!user) {
return []
}
if (!user) {return []}
return (
user?.tenants?.reduce((acc: string[], { tenant }) => {
if (tenant) {
@@ -15,9 +13,7 @@ export const getTenantAccessIDs = (user: null | User): string[] => {
}
export const getTenantAdminTenantAccessIDs = (user: null | User): string[] => {
if (!user) {
return []
}
if (!user) {return []}
return (
user?.tenants?.reduce((acc: string[], { roles, tenant }) => {

View File

@@ -1,6 +1,6 @@
MIT License
(The MIT License)
Copyright (c) 2018-2024 Payload CMS, Inc. <info@payloadcms.com>
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.134",
"version": "3.0.0-beta.127",
"private": true,
"type": "module",
"scripts": {
@@ -59,7 +59,6 @@
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
"dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts",
"dev:prod": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod",
"dev:prod:memorydb": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod --start-memory-db",
"dev:vercel-postgres": "cross-env PAYLOAD_DATABASE=vercel-postgres pnpm runts ./test/dev.ts",
"devsafe": "node ./scripts/delete-recursively.js '**/.next' && pnpm dev",
"docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start",
@@ -67,7 +66,7 @@
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
"force:build": "pnpm run build:core:force",
"lint": "turbo run lint --concurrency 1 --continue",
"lint-staged": "lint-staged",
"lint-staged": "node ./scripts/run-lint-staged.js",
"lint:fix": "turbo run lint:fix --concurrency 1 --continue",
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
"prepare": "husky",

View File

@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2018-2024 Payload CMS, Inc. <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.134",
"version": "3.0.0-beta.127",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -8,14 +8,6 @@
"directory": "packages/create-payload-app"
},
"license": "MIT",
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
"maintainers": [
{
"name": "Payload",
"email": "info@payloadcms.com",
"url": "https://payloadcms.com"
}
],
"type": "module",
"exports": {
"./types": {

View File

@@ -78,14 +78,15 @@ export async function createProject(args: {
)
await fse.copy(localTemplate, projectDir)
} else if ('url' in template) {
let templateUrl = template.url
if (cliArgs['--template-branch']) {
template.url = `${template.url.split('#')?.[0]}#${cliArgs['--template-branch']}`
templateUrl = `${template.url}#${cliArgs['--template-branch']}`
debug(`Using template url: ${templateUrl}`)
}
await downloadTemplate({
debug: cliArgs['--debug'],
name: template.name,
branch: 'beta',
projectDir,
template,
})
}

View File

@@ -2,35 +2,27 @@ import { Readable } from 'node:stream'
import { pipeline } from 'node:stream/promises'
import { x } from 'tar'
import type { ProjectTemplate } from '../types.js'
import { debug as debugLog } from '../utils/log.js'
export async function downloadTemplate({
debug,
name,
branch,
projectDir,
template,
}: {
debug?: boolean
branch: string
/**
* The name of the template to download
* Must be dir /templates/<name>
*/
name: string
projectDir: string
template: ProjectTemplate
}) {
const branchOrTag = template.url.split('#')?.[1] || 'beta'
const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${branchOrTag}`
const filter = `payload-${branchOrTag.replace(/^v/, '')}/templates/${template.name}/`
if (debug) {
debugLog(`Using template url: ${template.url}`)
debugLog(`Codeload url: ${url}`)
debugLog(`Filter: ${filter}`)
}
const url = `https://codeload.github.com/payloadcms/payload/tar.gz/${branch}`
const filter = `payload-${branch}/templates/${name}/`
await pipeline(
await downloadTarStream(url),
x({
cwd: projectDir,
filter: (p) => p.includes(filter),
strip: 2 + template.name.split('/').length,
strip: 2 + name.split('/').length,
}),
)
}

View File

@@ -32,13 +32,13 @@ type InitNextArgs = {
} & Pick<CliArgs, '--debug'>
type InitNextResult =
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
| {
isSrcDir: boolean
nextAppDir: string
payloadConfigPath: string
success: true
}
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
const { dbType: dbType, packageManager, projectDir } = args

View File

@@ -15,19 +15,19 @@ export async function installPackages(args: {
let stderr = ''
switch (packageManager) {
case 'bun':
case 'pnpm':
case 'yarn': {
if (packageManager === 'bun') {
warning('Bun support is untested.')
}
;({ exitCode, stderr } = await execa(packageManager, ['add', ...packagesToInstall], {
case 'npm': {
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
cwd: projectDir,
}))
break
}
case 'npm': {
;({ exitCode, stderr } = await execa('npm', ['install', '--save', ...packagesToInstall], {
case 'yarn':
case 'pnpm':
case 'bun': {
if (packageManager === 'bun') {
warning('Bun support is untested.')
}
;({ exitCode, stderr } = await execa(packageManager, ['add', ...packagesToInstall], {
cwd: projectDir,
}))
break

View File

@@ -71,7 +71,7 @@ const vercelBlobStorageReplacement: StorageAdapterReplacement = {
configReplacement: [
' vercelBlobStorage({',
' collections: {',
' media: true,',
' [Media.slug]: true,',
' },',
" token: process.env.BLOB_READ_WRITE_TOKEN || '',",
' }),',

View File

@@ -14,7 +14,6 @@ export function validateTemplate(templateName: string): boolean {
}
export function getValidTemplates(): ProjectTemplate[] {
// Starters _must_ be a valid template name from the templates/ directory
return [
{
name: 'blank',
@@ -29,11 +28,37 @@ export function getValidTemplates(): ProjectTemplate[] {
url: `https://github.com/payloadcms/payload/templates/website#v${PACKAGE_VERSION}`,
},
// Remove these until they have been updated for 3.0
// {
// name: 'blank',
// type: 'starter',
// description: 'Blank Template',
// url: 'https://github.com/payloadcms/payload/templates/blank',
// },
// {
// name: 'ecommerce',
// type: 'starter',
// description: 'E-commerce Template',
// url: 'https://github.com/payloadcms/payload/templates/ecommerce',
// },
// {
// name: 'plugin',
// type: 'plugin',
// description: 'Template for creating a Payload plugin',
// url: 'https://github.com/payloadcms/plugin-template#beta',
// url: 'https://github.com/payloadcms/payload-plugin-template#beta',
// },
// {
// name: 'payload-demo',
// type: 'starter',
// description: 'Payload demo site at https://demo.payloadcms.com',
// url: 'https://github.com/payloadcms/public-demo',
// },
// {
// name: 'payload-website',
// type: 'starter',
// description: 'Payload website CMS at https://payloadcms.com',
// url: 'https://github.com/payloadcms/website-cms',
// },
]
}

View File

@@ -205,7 +205,7 @@ export class Main {
}
if (debugFlag) {
debug(`Using templates from git tag: v${PACKAGE_VERSION}`)
debug(`Using templates from git tag: ${PACKAGE_VERSION}`)
}
const validTemplates = getValidTemplates()
@@ -217,16 +217,6 @@ export class Main {
}
switch (template.type) {
case 'plugin': {
await createProject({
cliArgs: this.args,
packageManager,
projectDir,
projectName,
template,
})
break
}
case 'starter': {
const dbDetails = await selectDb(this.args, projectName)
const payloadSecret = generateSecret()
@@ -248,6 +238,16 @@ export class Main {
})
break
}
case 'plugin': {
await createProject({
cliArgs: this.args,
packageManager,
projectDir,
projectName,
template,
})
break
}
}
info('Payload project successfully created!')

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