Compare commits
382 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b49c18767 | ||
|
|
423df3f83a | ||
|
|
149781af9e | ||
|
|
51539e6d44 | ||
|
|
ce6e8b7412 | ||
|
|
d22a5ad840 | ||
|
|
e3385e1243 | ||
|
|
d329d9f282 | ||
|
|
644519c539 | ||
|
|
2aab4dfb9d | ||
|
|
2b37bfd93e | ||
|
|
4d6871abc8 | ||
|
|
f059d5b59a | ||
|
|
82d8d28b2c | ||
|
|
2624b0c34b | ||
|
|
8f568411de | ||
|
|
6066f2896a | ||
|
|
c6bc387a88 | ||
|
|
5e0ee919d7 | ||
|
|
9fd545070b | ||
|
|
680863702e | ||
|
|
f20f6d70a0 | ||
|
|
983a939952 | ||
|
|
b579ec7bbd | ||
|
|
9703c34f4d | ||
|
|
d3dd1844dc | ||
|
|
54405732c9 | ||
|
|
6bf141c6d4 | ||
|
|
3be1cf16ef | ||
|
|
0cd585d09b | ||
|
|
c0636dfe22 | ||
|
|
0367ce5d5a | ||
|
|
38bc2e68fe | ||
|
|
b71483f557 | ||
|
|
3557480838 | ||
|
|
13cd1bd060 | ||
|
|
ed24292f55 | ||
|
|
b58a441109 | ||
|
|
eb8b9e2542 | ||
|
|
587a71c1b6 | ||
|
|
06a722ffdb | ||
|
|
e8ba315b20 | ||
|
|
95180f4737 | ||
|
|
558e6839bc | ||
|
|
f52eb88b70 | ||
|
|
cab10e2028 | ||
|
|
6ee2a762e8 | ||
|
|
4d092ab9cb | ||
|
|
da6e6c4441 | ||
|
|
2f9b9931e4 | ||
|
|
43af9146d8 | ||
|
|
99b3499044 | ||
|
|
cef864a80e | ||
|
|
44a4a99b92 | ||
|
|
34c785557d | ||
|
|
b83abf944b | ||
|
|
85658c7017 | ||
|
|
a0bf503f88 | ||
|
|
87aaccac94 | ||
|
|
d43ff8b4a7 | ||
|
|
e093e06926 | ||
|
|
baecb27712 | ||
|
|
faec969752 | ||
|
|
cf89d4cb56 | ||
|
|
57d2c8602f | ||
|
|
3233e903d9 | ||
|
|
5d6a3bc833 | ||
|
|
5eca243af9 | ||
|
|
2683bec738 | ||
|
|
0838b6837b | ||
|
|
885c73c838 | ||
|
|
730d472008 | ||
|
|
7b11dd053b | ||
|
|
c85d72317c | ||
|
|
d76c1f862c | ||
|
|
f1c5cfe1a5 | ||
|
|
68e0e5cbdd | ||
|
|
97ff9eeb1b | ||
|
|
e8eabde889 | ||
|
|
7d05069f36 | ||
|
|
4d871c27f6 | ||
|
|
709cc9c294 | ||
|
|
cd9b360292 | ||
|
|
1f6666785e | ||
|
|
53f8570a53 | ||
|
|
094b0399cb | ||
|
|
fd917e6bae | ||
|
|
b3c254e34f | ||
|
|
22a7ddfd28 | ||
|
|
0eda6b5c47 | ||
|
|
cd0becb7ba | ||
|
|
19b92c4748 | ||
|
|
c572057706 | ||
|
|
c86731aaf7 | ||
|
|
ffa0ea4756 | ||
|
|
0f7046b98e | ||
|
|
f582a254cd | ||
|
|
e067fa12b2 | ||
|
|
a5310ac1b3 | ||
|
|
fa4c9ba796 | ||
|
|
662839fb06 | ||
|
|
d07259d0dc | ||
|
|
4fa942f3a0 | ||
|
|
e2d0fc3e10 | ||
|
|
5e0ece5d72 | ||
|
|
6b5ab50f71 | ||
|
|
d7d906bc86 | ||
|
|
8cb80cecf8 | ||
|
|
5823a864f9 | ||
|
|
36d51de201 | ||
|
|
60c1d1d3d9 | ||
|
|
f0a45281b5 | ||
|
|
86584900b2 | ||
|
|
a7603bcc6b | ||
|
|
eeea06d6aa | ||
|
|
e3c3516283 | ||
|
|
5a45e60a73 | ||
|
|
e2413baf10 | ||
|
|
0c6a08da76 | ||
|
|
8401400129 | ||
|
|
e614a6fc70 | ||
|
|
5f302dd046 | ||
|
|
480bc0ad9d | ||
|
|
7274b1692d | ||
|
|
e263a9c9d4 | ||
|
|
34153dc85b | ||
|
|
5100fd35dc | ||
|
|
d10954b788 | ||
|
|
544a4dbd3a | ||
|
|
c84bcde0a1 | ||
|
|
85a765959b | ||
|
|
3ce6a45121 | ||
|
|
3248740f04 | ||
|
|
30e28603a7 | ||
|
|
0c03c2e3af | ||
|
|
4e48a4ffc5 | ||
|
|
f65e91dc74 | ||
|
|
6df3b381ef | ||
|
|
d849367a12 | ||
|
|
af4c6fb15a | ||
|
|
2856e0213f | ||
|
|
4fbf8aa0bf | ||
|
|
24788b8282 | ||
|
|
8090b2a23b | ||
|
|
64cf32146a | ||
|
|
15e6ee7dac | ||
|
|
8bda6eaa76 | ||
|
|
b8ad84c525 | ||
|
|
72b5817276 | ||
|
|
1fff7374d4 | ||
|
|
fec718e9e5 | ||
|
|
efe0b40aca | ||
|
|
db135129d8 | ||
|
|
b1a15f6e33 | ||
|
|
9037e6c64b | ||
|
|
604922a26e | ||
|
|
0117f18eb1 | ||
|
|
a715a4206e | ||
|
|
6e83edc988 | ||
|
|
9f1ebaf850 | ||
|
|
8571dc3965 | ||
|
|
5b8c721292 | ||
|
|
e2248a066d | ||
|
|
62dea6d52a | ||
|
|
fe380bd943 | ||
|
|
b266ae1356 | ||
|
|
4abcad67b7 | ||
|
|
0243371c71 | ||
|
|
15323a6b6d | ||
|
|
f5b9d32317 | ||
|
|
71ed0ad0ca | ||
|
|
b4fd2b1976 | ||
|
|
b6d2c95ee7 | ||
|
|
238232cf51 | ||
|
|
04cf39749b | ||
|
|
c9616b01ac | ||
|
|
dede06ad76 | ||
|
|
843d65f494 | ||
|
|
58788c327f | ||
|
|
0124ae8812 | ||
|
|
3d08222c29 | ||
|
|
f6c0251a6c | ||
|
|
cf32ee460c | ||
|
|
569ce08174 | ||
|
|
e2d370a415 | ||
|
|
152799ecc7 | ||
|
|
087ac31533 | ||
|
|
b184b26a4e | ||
|
|
f86e90b072 | ||
|
|
be04310810 | ||
|
|
d510961f6c | ||
|
|
d29f6775a8 | ||
|
|
7be836cced | ||
|
|
4528586849 | ||
|
|
47e82cb069 | ||
|
|
d38c6f8118 | ||
|
|
757c8647bf | ||
|
|
ba6c1b9439 | ||
|
|
ac92e1289b | ||
|
|
6a49bffa9a | ||
|
|
a95aada35a | ||
|
|
8fd430819b | ||
|
|
396760d523 | ||
|
|
6ab15fce1c | ||
|
|
81ca30c9d4 | ||
|
|
a5085b6e56 | ||
|
|
4cb8bbd2f4 | ||
|
|
b9c597af92 | ||
|
|
47b6bb683b | ||
|
|
d3df6c606c | ||
|
|
1cd578ef44 | ||
|
|
55dd4768b9 | ||
|
|
66946c8697 | ||
|
|
1e4e6e9619 | ||
|
|
c97fb62228 | ||
|
|
cd5a24e308 | ||
|
|
c17a03ef60 | ||
|
|
abfad8c088 | ||
|
|
82b4042b26 | ||
|
|
1129711f75 | ||
|
|
69582b2e8c | ||
|
|
00821bf0a2 | ||
|
|
4a313ee7c3 | ||
|
|
85f3a8b038 | ||
|
|
5fe4cb9eee | ||
|
|
0e120093cf | ||
|
|
064ab8846d | ||
|
|
1d5338a8dc | ||
|
|
4ac08dac65 | ||
|
|
41df1c447f | ||
|
|
808a283f74 | ||
|
|
be69ff5849 | ||
|
|
9d94c509e7 | ||
|
|
282f575cab | ||
|
|
6b61714d7e | ||
|
|
dc8ffa34e4 | ||
|
|
8c8c49c66d | ||
|
|
bb7c829c5e | ||
|
|
6bf60ff5d6 | ||
|
|
100d1e7220 | ||
|
|
c97732cef3 | ||
|
|
c398c8b2a2 | ||
|
|
cee40169f3 | ||
|
|
7d3b6d12e3 | ||
|
|
98b62df792 | ||
|
|
e2d9cd1eed | ||
|
|
f873a84f08 | ||
|
|
c1b1b81774 | ||
|
|
7333cc25d8 | ||
|
|
32957833f3 | ||
|
|
20fabb81e8 | ||
|
|
7644b031c1 | ||
|
|
dbc403319d | ||
|
|
c1d025e241 | ||
|
|
c3c3f3fc88 | ||
|
|
6f1d61aabc | ||
|
|
3d282d2523 | ||
|
|
ba66a0e801 | ||
|
|
0fcd3d8f28 | ||
|
|
ddda183090 | ||
|
|
f19fc9057d | ||
|
|
17e4d78bdc | ||
|
|
9ef954f430 | ||
|
|
767b68b0bd | ||
|
|
53d78e995b | ||
|
|
0119965287 | ||
|
|
45dc3cab11 | ||
|
|
73d27be947 | ||
|
|
a5bbb338b9 | ||
|
|
fd7cd0324d | ||
|
|
aa795d19d5 | ||
|
|
9019cc9101 | ||
|
|
cd64238a51 | ||
|
|
5ffa0ed6ee | ||
|
|
1889f00f8f | ||
|
|
0e2fbe0e8f | ||
|
|
70a84d90ad | ||
|
|
56072e952d | ||
|
|
f88f482837 | ||
|
|
ca55c4e55f | ||
|
|
a00809db8e | ||
|
|
e930abe741 | ||
|
|
b584d22b3a | ||
|
|
254524ab1b | ||
|
|
472a6cb232 | ||
|
|
ed8d7f2106 | ||
|
|
4239f8e592 | ||
|
|
55deb3f01b | ||
|
|
d09fba47f1 | ||
|
|
2420969375 | ||
|
|
055ff455fe | ||
|
|
f3be1fcc62 | ||
|
|
ec00edcdad | ||
|
|
e36be704a5 | ||
|
|
75e557b894 | ||
|
|
41a26289b3 | ||
|
|
c9cb1ea05c | ||
|
|
b3f326fead | ||
|
|
a1f4168d50 | ||
|
|
8d946f225e | ||
|
|
26a01afa1a | ||
|
|
35686a7dca | ||
|
|
34942daf94 | ||
|
|
0bdd159fb9 | ||
|
|
d6e7211539 | ||
|
|
713673bc42 | ||
|
|
d75da16fef | ||
|
|
cfdacea210 | ||
|
|
924eb1d0b5 | ||
|
|
6fcf4ebc48 | ||
|
|
badd59ac38 | ||
|
|
5b6392009d | ||
|
|
777a2292dc | ||
|
|
352f70a9e4 | ||
|
|
09f267c378 | ||
|
|
c2d2702a59 | ||
|
|
062771e0a7 | ||
|
|
d2571fa798 | ||
|
|
eafc0876a1 | ||
|
|
56ce92e012 | ||
|
|
51958c25ac | ||
|
|
8789dae155 | ||
|
|
d10f3f140f | ||
|
|
7670a23611 | ||
|
|
53811b2324 | ||
|
|
3344656e6a | ||
|
|
e4eece0352 | ||
|
|
2a4b821c34 | ||
|
|
4a4c4223e6 | ||
|
|
314ea43e6d | ||
|
|
fdbdf93250 | ||
|
|
cb33417924 | ||
|
|
cbf12524fe | ||
|
|
8b34cab19a | ||
|
|
05a99b5798 | ||
|
|
f1a83de772 | ||
|
|
eb3d299521 | ||
|
|
60afdd239c | ||
|
|
307840edd1 | ||
|
|
95b8df6d70 | ||
|
|
700f28c3ac | ||
|
|
1487c3ff16 | ||
|
|
b94772bfcd | ||
|
|
44d79d2153 | ||
|
|
09f3008569 | ||
|
|
e5043e73e3 | ||
|
|
16d308f452 | ||
|
|
3e9ad427ec | ||
|
|
16d0289c36 | ||
|
|
894a400b86 | ||
|
|
9134b51985 | ||
|
|
e00321a101 | ||
|
|
75d0b5bb50 | ||
|
|
b774d09b4b | ||
|
|
a91451e946 | ||
|
|
983bf713b3 | ||
|
|
f0ac9d6935 | ||
|
|
d2572ba4e4 | ||
|
|
51307f29c5 | ||
|
|
a608a799aa | ||
|
|
e7822f7a51 | ||
|
|
e3277cf120 | ||
|
|
2ffb2b5200 | ||
|
|
18698a9f18 | ||
|
|
95c96247d9 | ||
|
|
f2f65952b2 | ||
|
|
afe5f16af0 | ||
|
|
6a7da1e603 | ||
|
|
bc9adb9fe5 | ||
|
|
9e1153639e | ||
|
|
59e5a73e49 | ||
|
|
8d1856ae2d | ||
|
|
1eeee4854a | ||
|
|
3bea32883d | ||
|
|
4f2891836e | ||
|
|
63e86c4147 | ||
|
|
dfc77f0a3f | ||
|
|
1aaed161eb | ||
|
|
7d411622c6 | ||
|
|
a106fa62fe | ||
|
|
551c249e39 | ||
|
|
38028dd36b |
@@ -4,7 +4,7 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,ts}]
|
||||
[*.{js,ts,tsx}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
@@ -15,3 +15,7 @@ indent_size = 2
|
||||
[*.scss]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.mdx]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
66
.eslintrc.js
66
.eslintrc.js
@@ -1,19 +1,63 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: '@trbl',
|
||||
ignorePatterns: [
|
||||
'/**/*.d.ts'
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
],
|
||||
extends: [
|
||||
'@trbl',
|
||||
],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
rules: {
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
'import/no-unresolved': [
|
||||
2,
|
||||
{
|
||||
ignore: [
|
||||
'payload-config',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'import/no-unresolved': [
|
||||
2,
|
||||
"import/no-extraneous-dependencies": ["error", { "packageDir": "./" }],
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
|
||||
'import/prefer-default-export': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
'react/no-unused-prop-types': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
ignore: [
|
||||
'payload/config',
|
||||
'payload/unsanitizedConfig',
|
||||
],
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
'no-underscore-dangle': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
7
.github/workflows/tests.yml
vendored
7
.github/workflows/tests.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Node.js CI
|
||||
name: build
|
||||
|
||||
on: [push]
|
||||
|
||||
@@ -15,6 +15,9 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org
|
||||
scope: '@payloadcms'
|
||||
always-auth: true
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -25,6 +28,8 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: yarn
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- run: yarn test:client
|
||||
- run: yarn test:int # In-memory db + api tests
|
||||
env:
|
||||
|
||||
49
.vscode/launch.json
vendored
49
.vscode/launch.json
vendored
@@ -23,37 +23,40 @@
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug Jest Test - Current File",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"--inspect-brk",
|
||||
"${workspaceRoot}/node_modules/.bin/jest",
|
||||
"${fileBasename}",
|
||||
"--runInBand"
|
||||
],
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.js"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"port": 9229,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.js"
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts",
|
||||
"BABEL_ENV": "development"
|
||||
},
|
||||
"program": "${workspaceFolder}/demo/server.js",
|
||||
"program": "${workspaceFolder}/demo/index.js",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program - Production",
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts",
|
||||
"NODE_ENV": "production",
|
||||
"BABEL_ENV": "development"
|
||||
},
|
||||
"program": "${workspaceFolder}/demo/index.js",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
|
||||
72
README.md
72
README.md
@@ -1,21 +1,73 @@
|
||||
# Payload
|
||||
<h1 align="center">Payload</h1>
|
||||
<p align="center">A self-hosted, JavaScript headless CMS & application framework built with Express, MongoDB and React.</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/payloadcms/payload/actions">
|
||||
<img src="https://github.com/payloadcms/payload/workflows/build/badge.svg">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/payload">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/payload">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Headless CMS and application framework
|
||||
<a href="https://payloadcms.com">
|
||||
<img src="https://payloadcms.com/images/admin.jpg" alt="Payload headless CMS Admin panel built with React" />
|
||||
</a>
|
||||
|
||||
*TODO: More on why to use it and some features*
|
||||
### Quick Start
|
||||
|
||||
```
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
|
||||
|
||||
### Documentation
|
||||
|
||||
Check out the [Payload website](https://payloadcms.com/docs/getting-started/what-is-payload) to find in-depth documentation for everything that Payload offers.
|
||||
|
||||
### Features
|
||||
|
||||
- [GraphQL](https://payloadcms.com/docs/graphql/overview), [REST](https://payloadcms.com/docs/rest-api/overview), and [Local](https://payloadcms.com/docs/local-api/overview) APIs
|
||||
- [Easily customizable ReactJS Admin](https://payloadcms.com/docs/admin/overview)
|
||||
- [Fully self-hosted](https://payloadcms.com/docs/production/deployment)
|
||||
- [Extensible Authentication](https://payloadcms.com/docs/authentication/overview)
|
||||
- [Local file storage & upload](https://payloadcms.com/docs/upload/overview)
|
||||
- [Field-based Localization](https://payloadcms.com/docs/configuration/localization)
|
||||
- [Block-based Layout Builder](https://payloadcms.com/docs/fields/blocks)
|
||||
- [Extensible SlateJS rich text editor](https://payloadcms.com/docs/fields/rich-text)
|
||||
- [Array field type](https://payloadcms.com/docs/fields/array)
|
||||
- [Field conditional logic](https://payloadcms.com/docs/fields/overview#conditional-logic)
|
||||
- Extremely granular [Access Control](https://payloadcms.com/docs/access-control/overview)
|
||||
- [Document and field-level hooks](https://payloadcms.com/docs/hooks/overview) for every action Payload provides
|
||||
- Built with Typescript & very Typescript-friendly
|
||||
- Intensely fast API
|
||||
- Highly secure thanks to HTTP-only cookies, CSRF protection, and more
|
||||
|
||||
### Code-first
|
||||
|
||||
If you know JavaScript, you know Payload. Payload is a _code-first_ CMS, which allows us to do a lot of things right:
|
||||
|
||||
- Payload gives you everything you need, but then steps back and lets you build what you want in JavaScript or TypeScript - with no unnecessary complexity brought by GUIs. You'll understand how your CMS works, because you will have wrote it exactly how you want it.
|
||||
- Bring your own Express server and do whatever you need on top of Payload. Payload doesn't impose anything on you or your app.
|
||||
- Completely control the Admin panel by using your own React components. Swap out fields or even entire views with ease.
|
||||
- Use your data however and wherever you need thanks to auto-generated, yet fully extensible REST, GraphQL and Local Node APIs.
|
||||
|
||||
### Free for development use
|
||||
|
||||
Payload is 100% free for use on local environments such as `localhost` and staging sites such as `staging.mycoolsite.com`. When it's time to go to production, Payload charges a low and flat fee that doesn't change whatsoever based on your usage.
|
||||
|
||||
## Installation
|
||||
|
||||
`yarn add @payloadcms/payload` or `npm install @payloadcms/payload`
|
||||
Before beginning to work with Payload, make sure you have all of the [required software](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
## Usage
|
||||
From there, the easiest way to get started with Payload is to use the `create-payload-app` package:
|
||||
|
||||
*TODO: Show basic usage and link to docs*
|
||||
```
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
*TODO: Create Contributing.md*
|
||||
Alternatively, it only takes about five minutes to [write out your own app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
|
||||
|
||||
## License
|
||||
|
||||
*TODO: Create License.md*
|
||||
Find the Payload license [here](https://github.com/payloadcms/payload/blob/master/license.md).
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
require.resolve('@babel/preset-env'),
|
||||
{
|
||||
targets: [
|
||||
'defaults',
|
||||
'not IE 11',
|
||||
'not IE_Mob 11',
|
||||
],
|
||||
},
|
||||
],
|
||||
require.resolve('@babel/preset-react'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('@babel/plugin-transform-runtime'),
|
||||
require.resolve('@babel/plugin-proposal-class-properties'),
|
||||
require.resolve('@babel/plugin-proposal-optional-chaining'),
|
||||
],
|
||||
};
|
||||
const config = require('./src/babel.config');
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export {
|
||||
useForm,
|
||||
useFormFields,
|
||||
useWatchForm,
|
||||
useFormSubmitted,
|
||||
useFormProcessing,
|
||||
useFormModified,
|
||||
|
||||
2
config.d.ts
vendored
Normal file
2
config.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { buildConfig } from './dist/config/build';
|
||||
export * from './dist/config/types';
|
||||
1
config.js
Normal file
1
config.js
Normal file
@@ -0,0 +1 @@
|
||||
exports.buildConfig = require('./dist/config/build').buildConfig;
|
||||
@@ -6,11 +6,7 @@
|
||||
*/
|
||||
const checkRole = (allRoles, user) => {
|
||||
if (user) {
|
||||
if (allRoles.some((role) => {
|
||||
return user.roles && user.roles.some((individualRole) => {
|
||||
return individualRole === role;
|
||||
});
|
||||
})) {
|
||||
if (allRoles.some((role) => user.roles && user.roles.some((individualRole) => individualRole === role))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,4 +14,4 @@ const checkRole = (allRoles, user) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = checkRole;
|
||||
export default checkRole;
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
export default [
|
||||
'admin',
|
||||
'editor',
|
||||
'moderator',
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const CTA: Block = {
|
||||
slug: 'cta',
|
||||
labels: {
|
||||
singular: 'Call to Action',
|
||||
@@ -16,8 +18,9 @@ module.exports = {
|
||||
name: 'url',
|
||||
label: 'URL',
|
||||
type: 'text',
|
||||
height: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default CTA;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const Email: Block = {
|
||||
slug: 'email',
|
||||
labels: {
|
||||
singular: 'Email',
|
||||
@@ -9,8 +11,9 @@ module.exports = {
|
||||
name: 'testEmail',
|
||||
label: 'Test Email Field',
|
||||
type: 'email',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Email;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const NumberBlock: Block = {
|
||||
slug: 'number',
|
||||
labels: {
|
||||
singular: 'Number',
|
||||
@@ -9,8 +11,10 @@ module.exports = {
|
||||
name: 'testNumber',
|
||||
label: 'Test Number Field',
|
||||
type: 'number',
|
||||
maxLength: 100,
|
||||
max: 100,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default NumberBlock;
|
||||
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
blockImage: '/static/assets/images/generic-block-image.svg',
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const Quote: Block = {
|
||||
imageURL: '/static/assets/images/generic-block-image.svg',
|
||||
slug: 'quote',
|
||||
labels: {
|
||||
singular: 'Quote',
|
||||
@@ -17,7 +19,6 @@ module.exports = {
|
||||
name: 'quote',
|
||||
label: 'Quote',
|
||||
type: 'textarea',
|
||||
height: 100,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@@ -29,3 +30,5 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Quote;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Sidebar = () => <div className="sidebar">fake sidebar</div>
|
||||
|
||||
export default Sidebar;
|
||||
@@ -35,7 +35,7 @@ const insertButton = (editor, { href, label, style, newTab = false }) => {
|
||||
Transforms.insertNodes(editor, nodes);
|
||||
};
|
||||
|
||||
const ToolbarButton = ({ path }) => {
|
||||
const ToolbarButton: React.FC = ({ path }) => {
|
||||
const { open, closeAll } = useModal();
|
||||
const editor = useSlate();
|
||||
|
||||
@@ -5,7 +5,7 @@ import './index.scss';
|
||||
|
||||
const baseClass = 'rich-text-button';
|
||||
|
||||
const ButtonElement = ({ attributes, children, element }) => {
|
||||
const ButtonElement: React.FC = ({ attributes, children, element }) => {
|
||||
const { style = 'primary', label } = element;
|
||||
|
||||
return (
|
||||
@@ -1,4 +1,6 @@
|
||||
const withButton = (incomingEditor) => {
|
||||
import { Editor } from 'slate';
|
||||
|
||||
const withButton = (incomingEditor: Editor): Editor => {
|
||||
const editor = incomingEditor;
|
||||
const { isVoid } = editor;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const PurpleBackground = ({ attributes, children }) => (
|
||||
const PurpleBackground: React.FC<any> = ({ attributes, children }) => (
|
||||
<span
|
||||
{...attributes}
|
||||
style={{ backgroundColor: 'purple' }}
|
||||
@@ -1,8 +0,0 @@
|
||||
import button from './Button';
|
||||
import leaf from './Leaf';
|
||||
|
||||
export default {
|
||||
name: 'purple-background',
|
||||
button,
|
||||
leaf,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import Button from './Button';
|
||||
import Leaf from './Leaf';
|
||||
|
||||
export default {
|
||||
name: 'purple-background',
|
||||
Button,
|
||||
Leaf,
|
||||
};
|
||||
14
demo/client/index.html
Normal file
14
demo/client/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="portal"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,12 +1,13 @@
|
||||
const roles = require('../access/roles');
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import roles from '../access/roles';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const access = ({ req: { user } }) => {
|
||||
const result = checkRole(['admin'], user);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
const Admin: PayloadCollectionConfig = {
|
||||
slug: 'admins',
|
||||
labels: {
|
||||
singular: 'Admin',
|
||||
@@ -28,7 +29,7 @@ module.exports = {
|
||||
depth: 0,
|
||||
cookies: {
|
||||
secure: false,
|
||||
sameSite: 'Lax',
|
||||
sameSite: 'lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
@@ -45,6 +46,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
type: 'text',
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (checkRole(['admin'], user)) {
|
||||
@@ -67,3 +69,5 @@ module.exports = {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
};
|
||||
|
||||
export default Admin;
|
||||
@@ -1,10 +1,11 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
|
||||
const AllFields = {
|
||||
const AllFields: PayloadCollectionConfig = {
|
||||
slug: 'all-fields',
|
||||
labels: {
|
||||
singular: 'All Fields',
|
||||
@@ -12,14 +13,13 @@ const AllFields = {
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
disableScrollOnSuccess: true,
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
@@ -85,6 +85,37 @@ const AllFields = {
|
||||
required: true,
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'dateFieldExample',
|
||||
label: 'Day and Time',
|
||||
type: 'date',
|
||||
admin: {
|
||||
date: {
|
||||
timeIntervals: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dayOnlyDateFieldExample',
|
||||
label: 'Day Only',
|
||||
type: 'date',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
monthsToShow: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'timeOnlyDateFieldExample',
|
||||
label: 'Time Only',
|
||||
type: 'date',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'timeOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
@@ -101,6 +132,9 @@ const AllFields = {
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
@@ -189,10 +223,6 @@ const AllFields = {
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
name: 'blocks',
|
||||
minRows: 2,
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
@@ -272,4 +302,4 @@ const AllFields = {
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
module.exports = AllFields;
|
||||
export default AllFields;
|
||||
12
demo/collections/AutoLabel.ts
Normal file
12
demo/collections/AutoLabel.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const AutoLabel: PayloadCollectionConfig = {
|
||||
slug: 'auto-label',
|
||||
fields: [{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
}],
|
||||
};
|
||||
|
||||
export default AutoLabel;
|
||||
@@ -1,9 +1,10 @@
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
|
||||
module.exports = {
|
||||
const Blocks: PayloadCollectionConfig = {
|
||||
slug: 'blocks',
|
||||
labels: {
|
||||
singular: 'Blocks',
|
||||
@@ -38,3 +39,5 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Blocks;
|
||||
@@ -1,4 +1,6 @@
|
||||
const Code = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Code: PayloadCollectionConfig = {
|
||||
slug: 'code',
|
||||
labels: {
|
||||
singular: 'Code',
|
||||
@@ -17,4 +19,4 @@ const Code = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = Code;
|
||||
export default Code;
|
||||
@@ -1,4 +1,6 @@
|
||||
const Conditions = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Conditions: PayloadCollectionConfig = {
|
||||
slug: 'conditions',
|
||||
labels: {
|
||||
singular: 'Conditions',
|
||||
@@ -50,4 +52,4 @@ const Conditions = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = Conditions;
|
||||
export default Conditions;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Cell = () => <div className="description">fake description cell</div>;
|
||||
|
||||
export default Cell;
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const Cell: React.FC = () => <div className="description">fake description cell</div>;
|
||||
|
||||
export default Cell;
|
||||
@@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Description = () => <div className="description">fake description field</div>;
|
||||
|
||||
export default Description;
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const Description: React.FC = () => <div className="description">fake description field</div>;
|
||||
|
||||
export default Description;
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Filter = ({ onChange, value }) => (
|
||||
const Filter: React.FC<Props> = ({ onChange, value }) => (
|
||||
<input
|
||||
className="custom-description-filter"
|
||||
type="text"
|
||||
@@ -12,13 +12,4 @@ const Filter = ({ onChange, value }) => (
|
||||
/>
|
||||
);
|
||||
|
||||
Filter.defaultProps = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
Filter.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Filter;
|
||||
@@ -0,0 +1,4 @@
|
||||
export type Props = {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Group } from '../../../../../../../components/forms';
|
||||
|
||||
const CustomGroup = (props) => (
|
||||
const CustomGroup: React.FC = (props) => (
|
||||
<div className="custom-group">
|
||||
<Group {...props} />
|
||||
</div>
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DefaultList from '../../../../../../src/admin/components/views/collections/List/Default';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const CustomListView = (props) => (
|
||||
const CustomListView: React.FC = (props) => (
|
||||
<div className="custom-list">
|
||||
<p>This is a custom Pages list view</p>
|
||||
<p>Sup</p>
|
||||
@@ -12,12 +11,4 @@ const CustomListView = (props) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
CustomListView.propTypes = {
|
||||
collection: PropTypes.shape({
|
||||
labels: PropTypes.shape({
|
||||
plural: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default CustomListView;
|
||||
@@ -1,13 +1,14 @@
|
||||
const DescriptionField = require('./components/fields/Description/Field').default;
|
||||
const DescriptionCell = require('./components/fields/Description/Cell').default;
|
||||
const DescriptionFilter = require('./components/fields/Description/Filter').default;
|
||||
const NestedArrayField = require('./components/fields/NestedArrayCustomField/Field').default;
|
||||
const GroupField = require('./components/fields/Group/Field').default;
|
||||
const NestedGroupField = require('./components/fields/NestedGroupCustomField/Field').default;
|
||||
const NestedText1Field = require('./components/fields/NestedText1/Field').default;
|
||||
const ListView = require('./components/views/List').default;
|
||||
import { PayloadCollectionConfig } from '../../../src/collections/config/types';
|
||||
import DescriptionField from './components/fields/Description/Field';
|
||||
import DescriptionCell from './components/fields/Description/Cell';
|
||||
import DescriptionFilter from './components/fields/Description/Filter';
|
||||
import NestedArrayField from './components/fields/NestedArrayCustomField/Field';
|
||||
import GroupField from './components/fields/Group/Field';
|
||||
import NestedGroupField from './components/fields/NestedGroupCustomField/Field';
|
||||
import NestedText1Field from './components/fields/NestedText1/Field';
|
||||
import ListView from './components/views/List';
|
||||
|
||||
module.exports = {
|
||||
const CustomComponents: PayloadCollectionConfig = {
|
||||
slug: 'custom-components',
|
||||
labels: {
|
||||
singular: 'Custom Component',
|
||||
@@ -27,7 +28,6 @@ module.exports = {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
height: 100,
|
||||
required: true,
|
||||
localized: true,
|
||||
admin: {
|
||||
@@ -107,3 +107,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default CustomComponents;
|
||||
@@ -1,272 +0,0 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
|
||||
const DefaultValueTest = {
|
||||
slug: 'default-value-test',
|
||||
labels: {
|
||||
singular: 'Default Value Test',
|
||||
plural: 'Default Value Tests',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
defaultValue: 'Default Value',
|
||||
unique: true,
|
||||
access: {
|
||||
create: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: 'option-1',
|
||||
},
|
||||
{
|
||||
name: 'selectMany',
|
||||
label: 'Select w/ hasMany',
|
||||
type: 'select',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}, {
|
||||
value: 'option-4',
|
||||
label: 'Option 4 Label',
|
||||
}],
|
||||
defaultValue: ['option-1', 'option-4'],
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
name: 'radioGroupExample',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
}, {
|
||||
value: 'option-3',
|
||||
label: 'Option 3 Label',
|
||||
}],
|
||||
defaultValue: 'option-2',
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
defaultValue: 'some@email.com',
|
||||
}, {
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
defaultValue: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 1',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
defaultValue: 'nested default text 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'arrayText1',
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
defaultValue: 'default array text',
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => checkRole(['admin'], user),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'arrayText3',
|
||||
label: 'Array Text 3',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
label: 'Blocks Content',
|
||||
name: 'blocks',
|
||||
labels: {
|
||||
singular: 'Block',
|
||||
plural: 'Blocks',
|
||||
},
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to One Collection',
|
||||
name: 'relationship',
|
||||
relationTo: 'conditions',
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship hasMany',
|
||||
name: 'relationshipHasMany',
|
||||
relationTo: 'localized-posts',
|
||||
hasMany: true,
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
label: 'Relationship to Multiple Collections',
|
||||
name: 'relationshipMultipleCollections',
|
||||
relationTo: ['localized-posts', 'conditions'],
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: 'Textarea',
|
||||
name: 'textarea',
|
||||
defaultValue: 'my textarea text',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
localized: true,
|
||||
unique: true,
|
||||
defaultValue: 'my-slug',
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
type: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Rich Text',
|
||||
admin: {
|
||||
elements: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'ul',
|
||||
'ol',
|
||||
'link',
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
],
|
||||
},
|
||||
// defaultValue: 'sooo riiiiiiiich',
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
module.exports = DefaultValueTest;
|
||||
@@ -1,10 +1,11 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Email = require('../blocks/Email');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const NumberBlock = require('../blocks/Number');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
import Email from '../blocks/Email';
|
||||
import Quote from '../blocks/Quote';
|
||||
import NumberBlock from '../blocks/Number';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
|
||||
const DefaultValues = {
|
||||
const DefaultValues: PayloadCollectionConfig = {
|
||||
slug: 'default-values',
|
||||
labels: {
|
||||
singular: 'Default Value Test',
|
||||
@@ -13,12 +14,6 @@ const DefaultValues = {
|
||||
admin: {
|
||||
useAsTitle: 'text',
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.text) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.text.value}?preview=true&token=${token}`;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
@@ -189,7 +184,7 @@ const DefaultValues = {
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox',
|
||||
default: true,
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -203,6 +198,15 @@ const DefaultValues = {
|
||||
},
|
||||
blocks: [Email, NumberBlock, Quote, CallToAction],
|
||||
localized: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
blockType: 'email',
|
||||
testEmail: 'dev@payloadcms.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
@@ -280,4 +284,4 @@ const DefaultValues = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
module.exports = DefaultValues;
|
||||
export default DefaultValues;
|
||||
@@ -1,4 +1,5 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
const access = ({ req: { user } }) => {
|
||||
const isAdmin = checkRole(['admin'], user);
|
||||
@@ -18,7 +19,7 @@ const access = ({ req: { user } }) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
const Files: PayloadCollectionConfig = {
|
||||
slug: 'files',
|
||||
labels: {
|
||||
singular: 'File',
|
||||
@@ -65,3 +66,5 @@ module.exports = {
|
||||
useAsTitle: 'filename',
|
||||
},
|
||||
};
|
||||
|
||||
export default Files;
|
||||
@@ -1,4 +1,6 @@
|
||||
const HiddenFields = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const HiddenFields: PayloadCollectionConfig = {
|
||||
slug: 'hidden-fields',
|
||||
labels: {
|
||||
singular: 'Hidden Fields',
|
||||
@@ -15,7 +17,9 @@ const HiddenFields = {
|
||||
name: 'hiddenAdmin',
|
||||
type: 'text',
|
||||
label: 'Hidden on Admin',
|
||||
hidden: 'admin',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@@ -28,4 +32,4 @@ const HiddenFields = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = HiddenFields;
|
||||
export default HiddenFields;
|
||||
@@ -1,5 +1,8 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
module.exports = {
|
||||
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Hooks: PayloadCollectionConfig = {
|
||||
slug: 'hooks',
|
||||
labels: {
|
||||
singular: 'Hook',
|
||||
@@ -82,10 +85,11 @@ module.exports = {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
height: 100,
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Hooks;
|
||||
@@ -1,4 +1,6 @@
|
||||
const LocalOperations = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const LocalOperations: PayloadCollectionConfig = {
|
||||
slug: 'local-operations',
|
||||
labels: {
|
||||
singular: 'Local Operation',
|
||||
@@ -33,4 +35,4 @@ const LocalOperations = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = LocalOperations;
|
||||
export default LocalOperations;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const LocalizedPosts: PayloadCollectionConfig = {
|
||||
slug: 'localized-posts',
|
||||
labels: {
|
||||
singular: 'Localized Post',
|
||||
@@ -16,13 +18,6 @@ module.exports = {
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc && doc.title) {
|
||||
return `http://localhost:3000/posts/${doc.title.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -43,7 +38,6 @@ module.exports = {
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
height: 100,
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
@@ -57,3 +51,5 @@ module.exports = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default LocalizedPosts;
|
||||
@@ -1,4 +1,6 @@
|
||||
const LocalizedArrays = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const LocalizedArrays: PayloadCollectionConfig = {
|
||||
slug: 'localized-arrays',
|
||||
labels: {
|
||||
singular: 'Localized Array',
|
||||
@@ -47,4 +49,4 @@ const LocalizedArrays = {
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
module.exports = LocalizedArrays;
|
||||
export default LocalizedArrays;
|
||||
@@ -1,6 +1,6 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
module.exports = {
|
||||
const Media: PayloadCollectionConfig = {
|
||||
slug: 'media',
|
||||
labels: {
|
||||
singular: 'Media',
|
||||
@@ -44,20 +44,8 @@ module.exports = {
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'sizes',
|
||||
fields: [
|
||||
{
|
||||
name: 'icon',
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
const result = checkRole(['admin'], user);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Media;
|
||||
@@ -1,4 +1,6 @@
|
||||
const NestedArray = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const NestedArray: PayloadCollectionConfig = {
|
||||
slug: 'nested-arrays',
|
||||
labels: {
|
||||
singular: 'Nested Array',
|
||||
@@ -23,7 +25,7 @@ const NestedArray = {
|
||||
{
|
||||
name: 'parentIdentifier',
|
||||
label: 'Parent Identifier',
|
||||
defaultValue: '',
|
||||
defaultValue: ' ',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
@@ -67,4 +69,4 @@ const NestedArray = {
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
module.exports = NestedArray;
|
||||
export default NestedArray;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Preview: PayloadCollectionConfig = {
|
||||
slug: 'previewable-post',
|
||||
labels: {
|
||||
singular: 'Previewable Post',
|
||||
@@ -6,13 +8,13 @@ module.exports = {
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
preview: (doc, token) => {
|
||||
if (doc.title) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.title.value}?preview=true&token=${token}`;
|
||||
}
|
||||
preview: (doc, token) => {
|
||||
if (doc.title) {
|
||||
return `http://localhost:3000/previewable-posts/${doc.title.value}?preview=true&token=${token}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
@@ -27,3 +29,5 @@ module.exports = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
@@ -1,8 +1,9 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import checkRole from '../access/checkRole';
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const access = ({ req: { user } }) => checkRole(['admin'], user);
|
||||
|
||||
module.exports = {
|
||||
const PublicUsers: PayloadCollectionConfig = {
|
||||
slug: 'public-users',
|
||||
labels: {
|
||||
singular: 'Public User',
|
||||
@@ -35,10 +36,9 @@ module.exports = {
|
||||
verify: true,
|
||||
maxLoginAttempts: 5,
|
||||
lockTime: 600 * 1000, // lock time in ms
|
||||
generateVerificationUrl: (req, token) => `http://localhost:3000/api/verify?token=${token}`,
|
||||
cookies: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'Lax',
|
||||
sameSite: 'lax',
|
||||
domain: undefined,
|
||||
},
|
||||
},
|
||||
@@ -57,3 +57,5 @@ module.exports = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default PublicUsers;
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = {
|
||||
slug: 'relationship-a',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
labels: {
|
||||
singular: 'Relationship A',
|
||||
plural: 'Relationship A',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'post',
|
||||
label: 'Post',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-b',
|
||||
localized: false,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
40
demo/collections/RelationshipA.ts
Normal file
40
demo/collections/RelationshipA.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RelationshipA: PayloadCollectionConfig = {
|
||||
slug: 'relationship-a',
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
labels: {
|
||||
singular: 'Relationship A',
|
||||
plural: 'Relationship A',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'post',
|
||||
label: 'Post',
|
||||
type: 'relationship',
|
||||
relationTo: 'relationship-b',
|
||||
localized: true,
|
||||
},
|
||||
// {
|
||||
// name: 'LocalizedPost',
|
||||
// label: 'Localized Post',
|
||||
// type: 'relationship',
|
||||
// relationTo: 'localized-posts',
|
||||
// hasMany: true,
|
||||
// localized: true,
|
||||
// },
|
||||
{
|
||||
name: 'postLocalizedMultiple',
|
||||
label: 'Localized Post Multiple',
|
||||
type: 'relationship',
|
||||
relationTo: ['localized-posts', 'all-fields'],
|
||||
hasMany: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default RelationshipA;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RelationshipB: PayloadCollectionConfig = {
|
||||
slug: 'relationship-b',
|
||||
access: {
|
||||
read: () => true,
|
||||
@@ -19,3 +21,5 @@ module.exports = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default RelationshipB;
|
||||
@@ -1,7 +1,8 @@
|
||||
const Button = require('../client/components/richText/elements/Button').default;
|
||||
const PurpleBackground = require('../client/components/richText/leaves/PurpleBackground').default;
|
||||
import Button from '../client/components/richText/elements/Button';
|
||||
import PurpleBackground from '../client/components/richText/leaves/PurpleBackground';
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const RichText = {
|
||||
const RichText: PayloadCollectionConfig = {
|
||||
slug: 'rich-text',
|
||||
labels: {
|
||||
singular: 'Rich Text',
|
||||
@@ -34,4 +35,4 @@ const RichText = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = RichText;
|
||||
export default RichText;
|
||||
@@ -1,4 +1,6 @@
|
||||
const Select = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Select: PayloadCollectionConfig = {
|
||||
slug: 'select',
|
||||
labels: {
|
||||
singular: 'Select',
|
||||
@@ -40,4 +42,4 @@ const Select = {
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = Select;
|
||||
export default Select;
|
||||
@@ -1,6 +1,7 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
module.exports = {
|
||||
const StrictAccess: PayloadCollectionConfig = {
|
||||
slug: 'strict-access',
|
||||
labels: {
|
||||
singular: 'Strict Access',
|
||||
@@ -71,3 +72,5 @@ module.exports = {
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default StrictAccess;
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
|
||||
const Validations: PayloadCollectionConfig = {
|
||||
slug: 'validations',
|
||||
labels: {
|
||||
singular: 'Validation',
|
||||
@@ -106,5 +108,6 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
export default Validations;
|
||||
@@ -1,8 +1,9 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
const Quote = require('../blocks/Quote');
|
||||
const CallToAction = require('../blocks/CallToAction');
|
||||
import checkRole from '../access/checkRole';
|
||||
import Quote from '../blocks/Quote';
|
||||
import CallToAction from '../blocks/CallToAction';
|
||||
import { PayloadGlobalConfig } from '../../src/globals/config/types';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
slug: 'blocks-global',
|
||||
label: 'Blocks Global',
|
||||
access: {
|
||||
@@ -18,4 +19,4 @@ module.exports = {
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
} as PayloadGlobalConfig;
|
||||
@@ -1,6 +1,7 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadGlobalConfig } from '../../src/globals/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
slug: 'global-with-access',
|
||||
label: 'Global with Strict Access',
|
||||
access: {
|
||||
@@ -31,4 +32,4 @@ module.exports = {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
} as PayloadGlobalConfig;
|
||||
@@ -1,6 +1,7 @@
|
||||
const checkRole = require('../access/checkRole');
|
||||
import { PayloadGlobalConfig } from '../../src/globals/config/types';
|
||||
import checkRole from '../access/checkRole';
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
slug: 'navigation-array',
|
||||
label: 'Navigation Array',
|
||||
access: {
|
||||
@@ -24,4 +25,4 @@ module.exports = {
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
} as PayloadGlobalConfig;
|
||||
14
demo/index.js
Normal file
14
demo/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const babelConfig = require('../babel.config');
|
||||
|
||||
require('@babel/register')({
|
||||
...babelConfig,
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
env: {
|
||||
development: {
|
||||
sourceMaps: 'inline',
|
||||
retainLines: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
require('./server.ts');
|
||||
@@ -1,135 +0,0 @@
|
||||
const Admin = require('./collections/Admin');
|
||||
const AllFields = require('./collections/AllFields');
|
||||
const Code = require('./collections/Code');
|
||||
const Conditions = require('./collections/Conditions');
|
||||
const CustomComponents = require('./collections/CustomComponents');
|
||||
const File = require('./collections/File');
|
||||
const Blocks = require('./collections/Blocks');
|
||||
const DefaultValues = require('./collections/DefaultValues');
|
||||
const HiddenFields = require('./collections/HiddenFields');
|
||||
const Hooks = require('./collections/Hooks');
|
||||
const Localized = require('./collections/Localized');
|
||||
const LocalizedArray = require('./collections/LocalizedArray');
|
||||
const LocalOperations = require('./collections/LocalOperations');
|
||||
const Media = require('./collections/Media');
|
||||
const NestedArrays = require('./collections/NestedArrays');
|
||||
const Preview = require('./collections/Preview');
|
||||
const PublicUsers = require('./collections/PublicUsers');
|
||||
const RelationshipA = require('./collections/RelationshipA');
|
||||
const RelationshipB = require('./collections/RelationshipB');
|
||||
const RichText = require('./collections/RichText');
|
||||
const Select = require('./collections/Select');
|
||||
const StrictPolicies = require('./collections/StrictPolicies');
|
||||
const Validations = require('./collections/Validations');
|
||||
|
||||
const BlocksGlobal = require('./globals/BlocksGlobal');
|
||||
const NavigationArray = require('./globals/NavigationArray');
|
||||
const GlobalWithStrictAccess = require('./globals/GlobalWithStrictAccess');
|
||||
|
||||
module.exports = {
|
||||
admin: {
|
||||
user: 'admins',
|
||||
// indexHTML: 'custom-index.html',
|
||||
meta: {
|
||||
titleSuffix: '- Payload Demo',
|
||||
// ogImage: '/static/find-image-here.jpg',
|
||||
// favicon: '/img/whatever.png',
|
||||
},
|
||||
disable: false,
|
||||
components: {
|
||||
// Nav: () => (
|
||||
// <div>Hello</div>
|
||||
// ),
|
||||
},
|
||||
},
|
||||
email: {
|
||||
transport: 'mock',
|
||||
fromName: 'Payload',
|
||||
fromAddress: 'hello@payloadcms.com',
|
||||
},
|
||||
collections: [
|
||||
Admin,
|
||||
AllFields,
|
||||
Code,
|
||||
Conditions,
|
||||
CustomComponents,
|
||||
File,
|
||||
DefaultValues,
|
||||
Blocks,
|
||||
HiddenFields,
|
||||
Hooks,
|
||||
Localized,
|
||||
LocalizedArray,
|
||||
LocalOperations,
|
||||
Media,
|
||||
NestedArrays,
|
||||
Preview,
|
||||
PublicUsers,
|
||||
RelationshipA,
|
||||
RelationshipB,
|
||||
RichText,
|
||||
Select,
|
||||
StrictPolicies,
|
||||
Validations,
|
||||
],
|
||||
globals: [
|
||||
NavigationArray,
|
||||
GlobalWithStrictAccess,
|
||||
BlocksGlobal,
|
||||
],
|
||||
cookiePrefix: 'payload',
|
||||
serverURL: 'http://localhost:3000',
|
||||
cors: [
|
||||
'http://localhost',
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://localhost:8081',
|
||||
],
|
||||
csrf: [
|
||||
'http://localhost:3000',
|
||||
'https://other-app-here.com',
|
||||
],
|
||||
routes: {
|
||||
api: '/api',
|
||||
admin: '/admin',
|
||||
graphQL: '/graphql',
|
||||
graphQLPlayground: '/graphql-playground',
|
||||
},
|
||||
defaultDepth: 2,
|
||||
compression: {},
|
||||
paths: {
|
||||
scss: 'client/scss/overrides.scss',
|
||||
},
|
||||
graphQL: {
|
||||
maxComplexity: 1000,
|
||||
mutations: {},
|
||||
queries: {},
|
||||
disablePlaygroundInProduction: true,
|
||||
},
|
||||
rateLimit: {
|
||||
window: 15 * 60 * 100,
|
||||
max: 100,
|
||||
trustProxy: true,
|
||||
skip: (req) => req.ip === '127.0.0.1',
|
||||
},
|
||||
maxDepth: 10,
|
||||
localization: {
|
||||
locales: [
|
||||
'en',
|
||||
'es',
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
hooks: {
|
||||
afterError: (err) => {
|
||||
console.error('global error config handler', err);
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
limits: {
|
||||
fileSize: 10000000, // 10MB
|
||||
},
|
||||
},
|
||||
webpack: (config) => config,
|
||||
};
|
||||
132
demo/payload.config.ts
Normal file
132
demo/payload.config.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import path from 'path';
|
||||
import { buildConfig } from '../src/config/build';
|
||||
|
||||
import Admin from './collections/Admin';
|
||||
import AllFields from './collections/AllFields';
|
||||
import AutoLabel from './collections/AutoLabel';
|
||||
import Code from './collections/Code';
|
||||
import Conditions from './collections/Conditions';
|
||||
import CustomComponents from './collections/CustomComponents';
|
||||
import File from './collections/File';
|
||||
import Blocks from './collections/Blocks';
|
||||
import DefaultValues from './collections/DefaultValues';
|
||||
import HiddenFields from './collections/HiddenFields';
|
||||
import Hooks from './collections/Hooks';
|
||||
import Localized from './collections/Localized';
|
||||
import LocalizedArray from './collections/LocalizedArray';
|
||||
import LocalOperations from './collections/LocalOperations';
|
||||
import Media from './collections/Media';
|
||||
import NestedArrays from './collections/NestedArrays';
|
||||
import Preview from './collections/Preview';
|
||||
import PublicUsers from './collections/PublicUsers';
|
||||
import RelationshipA from './collections/RelationshipA';
|
||||
import RelationshipB from './collections/RelationshipB';
|
||||
import RichText from './collections/RichText';
|
||||
import Select from './collections/Select';
|
||||
import StrictPolicies from './collections/StrictPolicies';
|
||||
import Validations from './collections/Validations';
|
||||
|
||||
import BlocksGlobal from './globals/BlocksGlobal';
|
||||
import NavigationArray from './globals/NavigationArray';
|
||||
import GlobalWithStrictAccess from './globals/GlobalWithStrictAccess';
|
||||
|
||||
export default buildConfig({
|
||||
cookiePrefix: 'payload',
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
user: 'admins',
|
||||
indexHTML: path.resolve(__dirname, './client/index.html'),
|
||||
// meta: {
|
||||
// titleSuffix: '- Payload Demo',
|
||||
// // ogImage: '/static/find-image-here.jpg',
|
||||
// // favicon: '/img/whatever.png',
|
||||
// },
|
||||
disable: false,
|
||||
scss: path.resolve(__dirname, './client/scss/overrides.scss'),
|
||||
components: {
|
||||
// Nav: () => (
|
||||
// <div>Hello</div>
|
||||
// ),
|
||||
},
|
||||
webpack: (config) => config,
|
||||
},
|
||||
collections: [
|
||||
Admin,
|
||||
AllFields,
|
||||
AutoLabel,
|
||||
Code,
|
||||
Conditions,
|
||||
CustomComponents,
|
||||
File,
|
||||
DefaultValues,
|
||||
Blocks,
|
||||
HiddenFields,
|
||||
Hooks,
|
||||
Localized,
|
||||
LocalizedArray,
|
||||
LocalOperations,
|
||||
Media,
|
||||
NestedArrays,
|
||||
Preview,
|
||||
PublicUsers,
|
||||
RelationshipA,
|
||||
RelationshipB,
|
||||
RichText,
|
||||
Select,
|
||||
StrictPolicies,
|
||||
Validations,
|
||||
],
|
||||
globals: [
|
||||
NavigationArray,
|
||||
GlobalWithStrictAccess,
|
||||
BlocksGlobal,
|
||||
],
|
||||
cors: [
|
||||
'http://localhost',
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://localhost:8081',
|
||||
],
|
||||
csrf: [
|
||||
'http://localhost:3000',
|
||||
'https://other-app-here.com',
|
||||
],
|
||||
routes: {
|
||||
api: '/api',
|
||||
admin: '/admin',
|
||||
graphQL: '/graphql',
|
||||
graphQLPlayground: '/graphql-playground',
|
||||
},
|
||||
defaultDepth: 2,
|
||||
graphQL: {
|
||||
maxComplexity: 1000,
|
||||
mutations: {}, // TODO: needs typing
|
||||
queries: {}, // TODO: needs typing
|
||||
disablePlaygroundInProduction: true,
|
||||
},
|
||||
// rateLimit: {
|
||||
// window: 15 * 60 * 100,
|
||||
// max: 100,
|
||||
// trustProxy: true,
|
||||
// skip: (req) => req.ip === '127.0.0.1',
|
||||
// },
|
||||
maxDepth: 10,
|
||||
localization: {
|
||||
locales: [
|
||||
'en',
|
||||
'es',
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
hooks: {
|
||||
afterError: (err) => {
|
||||
console.error('global error config handler', err);
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
limits: {
|
||||
fileSize: 10000000, // 10MB
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,8 +1,7 @@
|
||||
/* eslint-disable no-console */
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const payload = require('../src');
|
||||
const logger = require('../src/utilities/logger')();
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import payload from '../src';
|
||||
|
||||
const expressApp = express();
|
||||
|
||||
@@ -12,9 +11,12 @@ payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: 'mongodb://localhost/payload',
|
||||
express: expressApp,
|
||||
onInit: () => {
|
||||
logger.info('Payload is initialized');
|
||||
// console.log('Payload is initialized');
|
||||
email: {
|
||||
fromName: 'Payload',
|
||||
fromAddress: 'hello@payloadcms.com',
|
||||
},
|
||||
onInit: (app) => {
|
||||
app.logger.info('Payload Demo Initialized');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,17 +34,6 @@ externalRouter.get('/', (req, res) => {
|
||||
|
||||
expressApp.use('/external-route', externalRouter);
|
||||
|
||||
exports.start = (cb) => {
|
||||
const server = expressApp.listen(3000, async () => {
|
||||
logger.info(`listening on ${3000}...`);
|
||||
|
||||
if (cb) cb();
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
// when server.js is launched directly
|
||||
if (module.id === require.main.id) {
|
||||
exports.start();
|
||||
}
|
||||
expressApp.listen(3000, async () => {
|
||||
payload.logger.info(`listening on ${3000}...`);
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Access Control Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to write access control.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Access Control Examples
|
||||
label: Examples
|
||||
order: 20
|
||||
---
|
||||
|
||||
Show examples of how to write access control functions.
|
||||
106
docs/Admin/components.mdx
Normal file
106
docs/Admin/components.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Swap in your own React components
|
||||
label: Custom Components
|
||||
order: 20
|
||||
---
|
||||
|
||||
While designing the Payload Admin panel, we determined it should be as minimal and straightforward as possible to allow easy customization and control. There are many times where you may want to completely control how a whole view or a field works. You might even want to add in your own routes entirely. In order for Payload to support that level of customization without introducing versioning / future-proofing issues, Payload provides for a pattern to supply your own React components via your Payload config.
|
||||
|
||||
To swap in your own React component, first, consult the list of available component overrides below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
Custom components will automatically be provided with all props that the default component would accept.
|
||||
</Banner>
|
||||
|
||||
### Base Component Overrides
|
||||
|
||||
You can override a set of admin panel-wide components by providing a component to your base Payload config's `admin.components` property. The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | -------------|
|
||||
| **`Nav`** | Contains the sidebar and mobile Nav in its entirety. |
|
||||
| **`views.Account`** | The Account view is used to show the currently logged in user's Account page. |
|
||||
| **`views.Dashboard`** | The main landing page of the Admin panel. |
|
||||
| **`graphics.Icon`** | Used as a graphic within the `Nav` component. Often represents a condensed version of a full logo. |
|
||||
| **`graphics.Logo`** | The full logo to be used in contexts like the `Login` view. |
|
||||
|
||||
#### Full example:
|
||||
|
||||
`payload.config.js`
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import { MyCustomNav, MyCustomLogo, MyCustomIcon, MyCustomAccount, MyCustomDashboard } from './customComponents.js';
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
components: {
|
||||
Nav: MyCustomNav,
|
||||
graphics: {
|
||||
Icon: MyCustomIcon,
|
||||
Logo: MyCustomLogo,
|
||||
},
|
||||
views: {
|
||||
Account: MyCustomAccount,
|
||||
Dashboard: MyCustomDashboard,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
*For more examples regarding how to customize components, look at the [demo app](https://github.com/payloadcms/payload/tree/master/demo).*
|
||||
|
||||
### Collections
|
||||
|
||||
You can override components on a Collection-by-Collection basis via each Collection's `admin` property.
|
||||
|
||||
| Path | Description |
|
||||
| ---------------- | -------------|
|
||||
| **`views.Edit`** | Used while a document within this Collection is being edited. |
|
||||
| **`views.List`** | The `List` view is used to render a paginated, filterable table of Documents in this Collection. |
|
||||
|
||||
### Globals
|
||||
|
||||
As with Collections, You can override components on a global-by-global basis via their `admin` property.
|
||||
|
||||
| Path | Description |
|
||||
| ---------------- | -------------|
|
||||
| **`views.Edit`** | Used while this Global is being edited. |
|
||||
|
||||
### Fields
|
||||
|
||||
All Payload fields support the ability to swap in your own React components. So, for example, instead of rendering a default Text input, you might need to render a color picker that provides the editor with a custom color picker interface to restrict the data entered to colors only.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
Don't see a built-in field type that you need? Build it! Using a combination of custom validation and custom components, you can override the entirety of how a component functions within the admin panel and effectively create your own field type.
|
||||
</Banner>
|
||||
|
||||
**Fields support the following custom components:**
|
||||
|
||||
| Component | Description |
|
||||
| --------------- | -------------|
|
||||
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. |
|
||||
|
||||
#### Sending and receiving values from the form
|
||||
|
||||
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useFieldType` hook as follows:
|
||||
|
||||
```js
|
||||
import { useFieldType } from 'payload/components/forms';
|
||||
|
||||
const CustomTextField = ({ path }) => {
|
||||
const { value, setValue } = useFieldType({ path });
|
||||
|
||||
return (
|
||||
<input
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
67
docs/Admin/customizing-css.mdx
Normal file
67
docs/Admin/customizing-css.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Customizing CSS & SCSS
|
||||
label: Customizing CSS
|
||||
order: 30
|
||||
---
|
||||
|
||||
### Adding your own CSS / SCSS
|
||||
|
||||
You can add your own CSS by providing your base Payload config with a path to your own CSS or SCSS. Customize the styling of any part of the Payload dashboard as necessary.
|
||||
|
||||
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
|
||||
|
||||
**Example in payload.config.js:**
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
|
||||
const config = buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Overriding SCSS variables
|
||||
|
||||
You can specify your own SCSS variable stylesheet that will allow for the override of Payload's base theme. This unlocks a ton of powerful theming and design options such as:
|
||||
|
||||
- Changing dashboard font families
|
||||
- Modifying color palette
|
||||
- Creating a dark theme
|
||||
- Etc.
|
||||
|
||||
To do so, provide your base Payload config with a path to your own SCSS variable sheet.
|
||||
|
||||
**Example in payload.config.js:**
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
|
||||
const config = buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
scss: path.resolve(__dirname, 'relative/path/to/vars.scss'),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Example stylesheet override:**
|
||||
```scss
|
||||
$font-body: 'Papyrus';
|
||||
$style-radius-m: 10px;
|
||||
```
|
||||
|
||||
To reference all Sass variables that you can override, look at the default [SCSS variable stylesheet](https://github.com/payloadcms/payload/blob/master/src/admin/scss/vars.scss) within the Payload source code.
|
||||
|
||||
<Banner type="error">
|
||||
<strong>Warning:</strong><br />
|
||||
Only SCSS variables, mixins, functions, and extends are allowed in <strong>your SCSS overrides</strong>. Do not attempt to add any CSS declarations to this file, as this variable stylesheet is imported by many components throughout the Payload Admin panel and will result in your CSS definition(s) being duplicated many times. If you need to add real CSS definitions, see "Adding your own CSS / SCSS" the top of this page.
|
||||
</Banner>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
73
docs/Admin/overview.mdx
Normal file
73
docs/Admin/overview.mdx
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: The Admin Panel
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Payload dynamically generates a beautiful, fully functional React admin panel to manage your data. It's extremely powerful and can be customized / extended upon easily by swapping in your own React components. You can add additional views, modify how built-in views look / work, swap out Payload branding for your client's, build your own field types and much more.
|
||||
|
||||
The Payload Admin panel is built with Webpack, code-split, highly performant (even with 100+ fields), and written fully in TypeScript.
|
||||
|
||||
<Banner type="success">
|
||||
The Admin panel is meant to be simple enough to give you a starting point but not bring too much complexity, so that you can easily customize it to suit the needs of your application and your editors.
|
||||
</Banner>
|
||||
|
||||

|
||||
|
||||
*Screenshot of the Admin panel while editing a document from an example `AllFields` collection*
|
||||
|
||||
## Admin Options
|
||||
|
||||
All options for the Admin panel are defined in your base Payload config file.
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | -------------|
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/master/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
|
||||
|
||||
### The Admin User Collection
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br />
|
||||
The Payload Admin panel can only be used by one Collection that supports <a href="/docs/authentication/">Authentication</a>.
|
||||
</Banner>
|
||||
|
||||
To specify which Collection to use to log in to the Admin panel, pass the `admin` options a `user` key equal to the slug of the Collection that you'd like to use.
|
||||
|
||||
`payload.config.js`:
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
|
||||
const config = buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
user: 'admins', // highlight-line
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
By default, if you have not specified a Collection, Payload will automatically provide you with a `User` Collection which will be used to access the Admin panel. You can customize or override the fields and settings of the default `User` Collection by passing your own collection using `users` as its `slug` to Payload. When this is done, Payload will use your provided `User` Collection instead of its default version.
|
||||
|
||||
**Note: you can use whatever Collection you'd like to access the Admin panel as long as the Collection supports Authentication. It doesn't need to be called `users`!**
|
||||
|
||||
For example, you may wish to have two Collections that both support `Authentication`:
|
||||
|
||||
- `admins` - meant to have a higher level of permissions to manage your data and access the Admin panel
|
||||
- `customers` - meant for end users of your app that should not be allowed to log into the Admin panel
|
||||
|
||||
This is totally possible. For the above scenario, by specifying `admin: { user: 'admins' }`, your Payload Admin panel will use `admins`. Any users logged in as `customers` will not be able to log in via the Admin panel.
|
||||
|
||||
### Restricting user access
|
||||
|
||||
If you would like to restrict which users from a single Collection can access the Admin panel, you can use the `admin` access control function. [Click here](/docs/access-control/overview#admin) to learn more.
|
||||
|
||||
### License enforcement
|
||||
|
||||
Payload requires a valid license key to be used on production domains. You can use it as much as you'd like locally and on staging / UAT domains, but when you deploy to production, you'll need a license key to activate Payload's Admin panel. For more information, [click here](/docs/production/licensing).
|
||||
173
docs/Admin/webpack.mdx
Normal file
173
docs/Admin/webpack.mdx
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
title: Webpack
|
||||
label: Webpack
|
||||
order: 40
|
||||
---
|
||||
|
||||
Payload uses Webpack 5 to build the Admin panel. It comes with support for many common functionalities such as SCSS and Typescript out of the box, but there are many cases where you may need to add support for additional functionalities.
|
||||
|
||||
To extend the Webpack config, add the `webpack` key to your base Payload config, and provide a function that accepts the default Webpack config as its only argument:
|
||||
|
||||
`payload.config.js`
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
// highlight-start
|
||||
webpack: (config) => {
|
||||
// Do something with the config here
|
||||
|
||||
return config;
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Aliasing server-only modules
|
||||
|
||||
A common use case for extending the Payload config is to alias server-only modules, thus preventing them from inclusion into the browser JavaScript bundle.
|
||||
|
||||
As the Payload config is used in both server **and** client contexts, you may find yourself writing code in your Payload config that may be incompatible with browser environments.
|
||||
|
||||
Examples of **non** browser-friendly packages:
|
||||
|
||||
- `fs`
|
||||
- `stripe`
|
||||
- `authorizenet`
|
||||
- `nodemailer`
|
||||
|
||||
You may rely on server-only packages such as the above to perform logic in access control functions, hooks, and other contexts (which is great!) but when you boot up your Payload app and try to view it in the browser, you'll likely run into missing dependency issues or other general incompatibilities.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
To avoid problems with server code making it to your Webpack bundle, you can use the <strong>alias</strong> Webpack feature to tell Webpack to avoid importing the modules you want to restrict to server-only.
|
||||
</Banner>
|
||||
|
||||
<strong>For example, let's say that you have a Collection called `Subscriptions` which relies on Stripe:</strong>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
`collections/Subscriptions/index.js`
|
||||
```js
|
||||
import createStripeSubscription from './hooks/createStripeSubscription';
|
||||
|
||||
const Subscription = {
|
||||
slug: 'subscriptions',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
createStripeSubscription,
|
||||
]
|
||||
}
|
||||
fields: [
|
||||
{
|
||||
name: 'stripeSubscriptionID',
|
||||
type: 'text',
|
||||
required: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default Subscription;
|
||||
```
|
||||
|
||||
The collection above features a `beforeChange` hook that creates a Stripe subscription whenever a Subscription document is created in Payload.
|
||||
|
||||
<strong>That hook might look something like this:</strong>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
`collections/Subscriptions/hooks/createStripeSubscription.js`
|
||||
```js
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
|
||||
const createStripeSubscription = async ({ data, operation }) => {
|
||||
if (operation === 'create') {
|
||||
const dataWithStripeID = {...data};
|
||||
|
||||
// use Stripe to create a Stripe subscription
|
||||
const subscription = await stripe.subscriptions.create({
|
||||
// Configure the subscription accordingly
|
||||
});
|
||||
|
||||
// Automatically add the Stripe subscription ID
|
||||
// to the data that will be saved to this Subscription doc
|
||||
dataWithStripeID.stripeSubscriptionID = subscription.id;
|
||||
|
||||
return dataWithStripeID
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default createStripeSubscription;
|
||||
```
|
||||
|
||||
<Banner type="error">
|
||||
<strong>Warning:</strong><br/>
|
||||
The above code is NOT production-ready and should not be referenced to create Stripe subscriptions. Although creating a beforeChange hook is a completely valid spot to do things like create subscriptions, the code above is incomplete and insecure, meant for explanation purposes only.
|
||||
</Banner>
|
||||
|
||||
**As-is, this collection will prevent your Admin panel from bundling or loading correctly, because Stripe relies on some Node-only packages.**
|
||||
|
||||
To remedy this issue you can extend the Payload Webpack config to alias your entire `createStripeSubscription` hook to a separate, empty mock file.
|
||||
|
||||
Example in `payload.config.js`:
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import path from 'path';
|
||||
import Subscription from './collections/Subscription';
|
||||
|
||||
const createStripeSubscriptionPath = path.resolve(__dirname, 'collections/Subscription/hooks/createStripeSubscription.js');
|
||||
const mockModulePath = path.resolve(__dirname, 'mocks/emptyObject.js');
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'http://localhost:3000',
|
||||
collections: [
|
||||
Subscription
|
||||
],
|
||||
admin: {
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
[createStripeSubscriptionPath]: mockModulePath,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The above code will alias the file at path `createStripeSubscriptionPath` to a mocked module, which might look like this:
|
||||
|
||||
`mocks/emptyObject.js`
|
||||
```js
|
||||
export default {};
|
||||
```
|
||||
|
||||
Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too.
|
||||
|
||||
## Admin environment vars
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong><br />
|
||||
Be careful about what variables you provide to your client-side code. Analyze every single one to make sure that you're not accidentally leaking anything that an attacker could exploit. Only keys that are safe to be available to everyone in plain text should be provided to your Admin panel.
|
||||
</Banner>
|
||||
|
||||
By default, `env` variables are **not** provided to the Admin panel for security and safety reasons. But, Payload provides you with a way to still provide `env` vars to your frontend code.
|
||||
|
||||
**Payload will automatically supply any present `env` variables that are prefixed with `PAYLOAD_PUBLIC_` directly to the Admin panel.**
|
||||
|
||||
For example, if you've got the following environment variable:
|
||||
|
||||
`PAYLOAD_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_XXXXXXXXXXXXXXXXXX`
|
||||
|
||||
This key will automatically be made available to the Payload bundle and can be referenced in your Admin component code as `process.env.PAYLOAD_PUBLIC_STRIPE_PUBLISHABLE_KEY`.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: Accessing the User
|
||||
label: Accessing the User
|
||||
order: 40
|
||||
---
|
||||
|
||||
Talk about how to access the User in custom code like hooks and access control. The User is on the req.
|
||||
|
||||
Show code examples.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: Authentication Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to configure authentication here.
|
||||
|
||||
Need to cover:
|
||||
|
||||
1. What collection to use for Admin panel and how there can only be one
|
||||
1. Can have multiple auth collections
|
||||
1. Token expiration
|
||||
1. Email verification
|
||||
1. Security (max login, lock time)
|
||||
1. API keys
|
||||
1. Depth to populate (performance impact)
|
||||
1. Cookie settings (HTTP only discussion)
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: Authentication Operations
|
||||
label: Operations
|
||||
order: 30
|
||||
---
|
||||
|
||||
Talk about Auth operations here.
|
||||
|
||||
#### Me
|
||||
|
||||
Returns null or logged in user with token (useful for HTTP only)
|
||||
|
||||
#### Create operation modifications
|
||||
|
||||
Discuss if verification is required, etc etc
|
||||
|
||||
#### Init
|
||||
|
||||
Checks if there have been users created, checks Admin access
|
||||
|
||||
#### Login
|
||||
|
||||
HTTP-only cookies, token response. Discuss max login attempts
|
||||
|
||||
#### Logout
|
||||
|
||||
Removes HTTP-only cookie
|
||||
|
||||
#### Refresh
|
||||
|
||||
Refreshes token (requires valid token)
|
||||
|
||||
#### Register First User
|
||||
|
||||
Allows for anyone to register first user through UI
|
||||
|
||||
#### Forgot Password
|
||||
|
||||
Takes an email, sends email to that address. Discuss how to customize email
|
||||
|
||||
#### Access
|
||||
|
||||
Shows what the user can and cant do.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Authentication Security
|
||||
label: Security
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how Payload securely authenticates here.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Using the Payload Auth Middleware
|
||||
label: Using the Middleware
|
||||
order: 50
|
||||
---
|
||||
|
||||
Talk about how to use `payload.authenticate()` outside of Payload - show examples
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Custom Components
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to build custom components. Show a list of all components that can be swapped out.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Block Configs
|
||||
label: Blocks
|
||||
order: 40
|
||||
---
|
||||
|
||||
Talk about how to write block configs here.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Collection Configs
|
||||
label: Collections
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how to write collection configs here.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Global Configs
|
||||
label: Globals
|
||||
order: 30
|
||||
---
|
||||
|
||||
Talk about how to write Globals configs here.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: The Main Config
|
||||
label: Main
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to write the main config here.
|
||||
@@ -1,298 +0,0 @@
|
||||
---
|
||||
title: Fields Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
<Banner type="info">
|
||||
Fields are the building blocks of Payload. Collections and Globals both use Fields to define the shape of the data that they store. Payload offers a wide-array of field-types - both simple and complex.
|
||||
</Banner>
|
||||
|
||||
## Field types
|
||||
|
||||
The `type` property on a field determines how the input will be rendered in the admin interface, what values it can accept, and how it is presented in the API.
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'my-field',
|
||||
type: 'text', // highlight-line
|
||||
label: 'Text',
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Types
|
||||
|
||||
#### `text`
|
||||
|
||||
Plain text
|
||||
|
||||
#### `textarea`
|
||||
|
||||
Large blocks of text. No formatting.
|
||||
|
||||
#### `number`
|
||||
|
||||
Simple number input
|
||||
|
||||
#### `checkbox`
|
||||
|
||||
Boolean value that renders a checkbox
|
||||
|
||||
#### `date`
|
||||
|
||||
Renders date-picker
|
||||
|
||||
#### `email`
|
||||
|
||||
Email. Includes validation.
|
||||
|
||||
#### `code`
|
||||
|
||||
Monospaced code block
|
||||
|
||||
#### `richText`
|
||||
|
||||
Large formatted text. Includes rich text editor in the admin interface.
|
||||
|
||||
#### `select`
|
||||
|
||||
Renders a select box with any number of options
|
||||
|
||||
Options:
|
||||
|
||||
- `hasMany` - Allows for multiple options to be selected
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'select',
|
||||
label: 'Select',
|
||||
type: 'select',
|
||||
hasMany: true // Optional
|
||||
options: [
|
||||
{
|
||||
value: 'option-1',
|
||||
label: 'Option 1 Label',
|
||||
}, {
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
#### `radio`
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'radioGroup',
|
||||
label: 'Radio Group Example',
|
||||
type: 'radio',
|
||||
options: [
|
||||
{
|
||||
value: 'option-1',
|
||||
label: 'Options 1 Label',
|
||||
},
|
||||
{
|
||||
value: 'option-2',
|
||||
label: 'Option 2 Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
### Complex Types
|
||||
|
||||
More powerful types but require more complex configuration
|
||||
|
||||
#### `upload`
|
||||
|
||||
Allows file and image upload. Useful for uploaded assets that can be referenced and re-used.
|
||||
|
||||
_TODO: Link to Upload configuration docs_
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
label: 'Image',
|
||||
relationTo: 'media', // A collection with the `upload` properties configured
|
||||
}
|
||||
```
|
||||
|
||||
#### `row`
|
||||
|
||||
Rows allow for deeper nesting of values. They have their own set of fields.
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
label: 'Number',
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### `array`
|
||||
|
||||
An array of the specified fields
|
||||
|
||||
Options:
|
||||
|
||||
- `minRows` - Minimum number of items required
|
||||
- `maxRows` - Maximum number of items allowed
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'array',
|
||||
label: 'Array',
|
||||
type: 'array',
|
||||
minRows: 2, // Optional
|
||||
maxRows: 4, // Optional
|
||||
fields: [
|
||||
{
|
||||
name: 'textField',
|
||||
label: 'Text Field',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'checkbox',
|
||||
label: 'Checkbox',
|
||||
type: 'checkbox'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `group`
|
||||
|
||||
A way to group related fields together. They will be rendered as a group in the admin interface.
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'nestedText1',
|
||||
label: 'Nested Text 1',
|
||||
type: 'text',
|
||||
}, {
|
||||
name: 'nestedText2',
|
||||
label: 'Nested Text 2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
```
|
||||
|
||||
#### `blocks`
|
||||
|
||||
Repeat any type of content any number of times
|
||||
|
||||
The `blocks` property takes an array of the allowed fields
|
||||
|
||||
_TODO: Link to more powerful and complex examples_
|
||||
|
||||
Options:
|
||||
|
||||
- `minRows` - Minimum number of items required
|
||||
- `maxRows` - Maximum number of items allowed
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'page',
|
||||
type: 'blocks',
|
||||
label: 'Page',
|
||||
labels: {
|
||||
singular: 'Page',
|
||||
plural: 'Pages',
|
||||
},
|
||||
minRows: 2, // Optional
|
||||
maxRows: 4, // Optional
|
||||
blocks: [
|
||||
{
|
||||
slug: 'quote'
|
||||
label: 'Quote',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
slug: 'testimonial',
|
||||
label: 'Testimonial',
|
||||
type: 'textarea'
|
||||
},
|
||||
{
|
||||
slug: 'cta',
|
||||
label: 'Call To Action',
|
||||
type: 'text'
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Common Options
|
||||
|
||||
Most fields have the ability to use the following options
|
||||
|
||||
#### `defaultValue`
|
||||
|
||||
Initial value for the field
|
||||
|
||||
#### `required`
|
||||
|
||||
`true/false` value if the field is required to be populated
|
||||
|
||||
#### `localized`
|
||||
|
||||
`true/false` value to enable localization on the field
|
||||
|
||||
#### `admin`
|
||||
|
||||
The `admin` is an optional object with properties that specify how the field should be represented in the admin interface.
|
||||
|
||||
Properties:
|
||||
|
||||
- `position` - If specified as `sidebar`, the field will show in the admin interface's sidebar
|
||||
- `width` - A percentage value ie. `50%` that will control the max-width of the field
|
||||
- `readOnly` - `true/false` to set the field to read-only after it is saved
|
||||
|
||||
### Common Options Example
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'my-field',
|
||||
type: 'text',
|
||||
label: 'Text'
|
||||
// highlight-start
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
width: '50%',
|
||||
readOnly: true,
|
||||
}
|
||||
// highlight-end
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: GraphQL Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Go over GraphQL configuration options.
|
||||
|
||||
- Naming conventions
|
||||
- List of all queries and mutations w/ examples
|
||||
- Context
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Adding your own Queries and Mutations
|
||||
label: Custom Queries and Mutations
|
||||
order: 20
|
||||
---
|
||||
|
||||
Talk about how to add your own queries and mutations.
|
||||
@@ -1,253 +0,0 @@
|
||||
---
|
||||
title: Blog API From Scratch
|
||||
label: Blog API
|
||||
order: 20
|
||||
---
|
||||
|
||||
In this guide, we will be creating a Blog API from scratch.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Node
|
||||
- npm or yarn
|
||||
- MongoDB server
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Let's get started by creating a project in a blank directory
|
||||
|
||||
```sh
|
||||
yarn init -y
|
||||
```
|
||||
|
||||
We then will install Payload and Express
|
||||
|
||||
```sh
|
||||
yarn add payload express
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
Create a file named `server.js` in the root folder with the following contents
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.');
|
||||
});
|
||||
```
|
||||
|
||||
This is a basic Express application. Now let's pull in Payload and fill in the `init` parameters. `secret` and `mongoURL` can be changed as needed.
|
||||
|
||||
```js
|
||||
const express = require('express');
|
||||
const payload = require('payload'); // highlight-line
|
||||
|
||||
const app = express();
|
||||
|
||||
// highlight-start
|
||||
payload.init({
|
||||
secret: 'SECRET_KEY',
|
||||
mongoURL: 'mongodb://localhost/payload-blog',
|
||||
express: app,
|
||||
})
|
||||
// highlight-end
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.');
|
||||
});
|
||||
```
|
||||
|
||||
### Payload Configuration
|
||||
|
||||
Next, let's get some basics put into our `payload.config.js` file
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
serverURL: 'http://localhost:3000',
|
||||
collections: [],
|
||||
}
|
||||
```
|
||||
|
||||
#### Collections
|
||||
|
||||
We'll need a few different collections for our blog:
|
||||
|
||||
- Posts
|
||||
- Categories
|
||||
- Tags
|
||||
|
||||
The Posts will have the ability to have a single category and multiple tags
|
||||
|
||||
Let's create a `collections` directory and create a `.js` file for each
|
||||
|
||||
```js:title=Posts.js
|
||||
module.exports = {
|
||||
slug: 'posts',
|
||||
labels: {
|
||||
singular: 'Post',
|
||||
plural: 'Posts',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
label: 'Category',
|
||||
type: 'relationship',
|
||||
relationTo: 'categories', // Categories Slug // highlight-line
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
type: 'relationship',
|
||||
relationTo: 'tags', // Tags Slug // highlight-line
|
||||
hasMany: true, // Allow multiple tags per post
|
||||
},
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
```js:title=Categories.js
|
||||
module.exports = {
|
||||
slug: 'categories',
|
||||
labels: {
|
||||
singular: 'Category',
|
||||
plural: 'Categories',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
```js:title=Tags.js
|
||||
module.exports = {
|
||||
slug: 'tags',
|
||||
labels: {
|
||||
singular: 'Tag',
|
||||
plural: 'Tags',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Once these are created, we can pull them into the `collections` array in our `payload.config.js`
|
||||
|
||||
```js:title=payload.config.js
|
||||
const Admins = require('./collections/Admins');
|
||||
// highlight-start
|
||||
const Posts = require('./collections/Posts');
|
||||
const Categories = require('./collections/Categories');
|
||||
const Tags = require('./collections/Tags');
|
||||
// highlight-end
|
||||
|
||||
module.exports = {
|
||||
serverURL: 'http://localhost:3000',
|
||||
admin: {
|
||||
user: 'admins',
|
||||
},
|
||||
collections: [
|
||||
Admins,
|
||||
// highlight-start
|
||||
Posts,
|
||||
Categories,
|
||||
Tags
|
||||
// highlight-end
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Create Content
|
||||
|
||||
**TODO: Should a guide like this use the Admin interface or `curl`?**
|
||||
|
||||
Navigate to the admin interface at `http://localhost:3000/admin` and create your first user.
|
||||
|
||||
Then create some Categories, Tags, and a Post.
|
||||
|
||||
Let's use `curl` to quickly create some data. The following commands will create 2 tags and 1 category.
|
||||
|
||||
```sh
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "JavaScript"}' http://localhost:3000/api/tags
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "TypeScript"}' http://localhost:3000/api/tags
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name": "Code"}' http://localhost:3000/api/categories
|
||||
```
|
||||
|
||||
We'll then make a request to create a Post, this will inclue a relationship to the category and tags created previously.
|
||||
|
||||
TODO: Somehow retrieve tag and category IDs then include them in a request to create a Post
|
||||
|
||||
Once complete, navigate to `http://localhost:3000/api/posts`, and you should see something similar to the following:
|
||||
|
||||
```js
|
||||
{
|
||||
"docs": [
|
||||
{
|
||||
"tags": [
|
||||
{
|
||||
"name": "TypeScript",
|
||||
"createdAt": "2020-11-13T11:48:05.993Z",
|
||||
"updatedAt": "2020-11-13T11:48:05.993Z",
|
||||
"id": "5fae72758315da656fb3a8f0"
|
||||
},
|
||||
{
|
||||
"name": "JavaScript",
|
||||
"createdAt": "2020-11-13T11:48:00.064Z",
|
||||
"updatedAt": "2020-11-13T11:48:00.064Z",
|
||||
"id": "5fae72708315da656fb3a8ef"
|
||||
}
|
||||
],
|
||||
"title": "My Title",
|
||||
"content": "This is some content",
|
||||
"category": {
|
||||
"name": "Code",
|
||||
"createdAt": "2020-11-13T11:48:10.351Z",
|
||||
"updatedAt": "2020-11-13T11:48:36.358Z",
|
||||
"id": "5fae727a8315da656fb3a8f1"
|
||||
},
|
||||
"createdAt": "2020-11-13T11:50:14.312Z",
|
||||
"updatedAt": "2020-11-13T11:50:14.312Z",
|
||||
"id": "5fae72f68e314b67609e05d1"
|
||||
}
|
||||
],
|
||||
"totalDocs": 1,
|
||||
"limit": 10,
|
||||
"totalPages": 1,
|
||||
"page": 1,
|
||||
"pagingCounter": 1,
|
||||
"hasPrevPage": false,
|
||||
"hasNextPage": false,
|
||||
"prevPage": null,
|
||||
"nextPage": null
|
||||
}
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Configure Email
|
||||
label: Configure Email
|
||||
order: 30
|
||||
---
|
||||
|
||||
TODO: Configure SendGrid or similar
|
||||
@@ -1,320 +0,0 @@
|
||||
---
|
||||
title: Hooks Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
<Banner type="info">
|
||||
Hooks allow intervention before or after any internal operation.
|
||||
|
||||
Payload has extensive support for hooks, at both the document-level and field-level.
|
||||
</Banner>
|
||||
|
||||
## Hook Types
|
||||
|
||||
- [Collection Hooks](#collection-hooks)
|
||||
- [Field Hooks](#field-hooks)
|
||||
- [Error Hook](#error-hook)
|
||||
|
||||
## Collection Hooks
|
||||
|
||||
Collection can be executed at any point in the modification or retrieval of a collection.
|
||||
|
||||
Collection Hooks available:
|
||||
|
||||
_TODO: Fix anchor links_
|
||||
|
||||
- [beforeOperation](#beforeOperation)
|
||||
- [beforeValidate](#beforeValidate)
|
||||
- [beforeChange](#beforeChange)
|
||||
- [afterChange](#afterChange)
|
||||
- [beforeRead](#beforeRead)
|
||||
- [afterRead](#afterRead)
|
||||
- [beforeDelete](#beforeDelete)
|
||||
- [afterDelete](#afterDelete)
|
||||
|
||||
### Usage
|
||||
|
||||
Configuration of collection hooks is done under a collection config in `payload.config.js`. They can either be created in-line or required from a separate file. Each hook is an array of functions to be run.
|
||||
|
||||
_TODO: Talk about async vs synchronous and concurrency_
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
module.exports = {
|
||||
slug: 'public-user',
|
||||
fields: [
|
||||
{ name: 'name', label: 'Name', type: 'text'},
|
||||
]
|
||||
hooks: {
|
||||
// Before All Operations
|
||||
beforeOperation: [(args) => {...}],
|
||||
|
||||
// Create and Update Operations
|
||||
beforeValidate: [(args) => {...}],
|
||||
beforeChange: [(args) => {...}],
|
||||
afterChange: [(args) => {...}],
|
||||
|
||||
// Read Operations
|
||||
beforeRead: [(args) => {...}],
|
||||
afterRead: [(args) => {...}],
|
||||
|
||||
// Delete Operations
|
||||
beforeDelete: [(args) => {...}],
|
||||
afterDelete: [(args) => {...}],
|
||||
|
||||
// Login Operations
|
||||
beforeLogin: [(args) => {...}],
|
||||
afterLogin: [(args) => {...}],
|
||||
|
||||
// After error
|
||||
afterError: [(args) => {...}],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each hook is an array of functions allowing for multiple to be combined as desired.
|
||||
|
||||
Configuration of hooks should be done in your `payload.config.js`
|
||||
|
||||
#### beforeOperation
|
||||
|
||||
Runs before any operation
|
||||
|
||||
```js
|
||||
const beforeOperationHook = ({
|
||||
args, // Original arguments passed into the operation
|
||||
operation, // name of the operation
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
Available Operations: `create`, `read`, `update`, `delete`, `refresh`, `forgotPassword`
|
||||
|
||||
#### beforeValidate
|
||||
|
||||
Runs before the `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const beforeValidateHook = ({
|
||||
data, // incoming document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
originalDoc, // original document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeChange
|
||||
|
||||
Runs before `create` and `update` operations, after validation.
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
data, // incoming document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
originalDoc, // original document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterChange
|
||||
|
||||
Runs after `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const afterChangeHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeRead
|
||||
|
||||
Runs before `find` and `findByID` operations.
|
||||
|
||||
```js
|
||||
const beforeReadHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
query, // JSON formatted query
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterRead
|
||||
|
||||
Runs after `find` and `findByID` operations.
|
||||
|
||||
```js
|
||||
const afterReadHook = ({
|
||||
doc, // full document data
|
||||
req, // full express request
|
||||
query, // JSON formatted query
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeDelete
|
||||
|
||||
Runs before `delete` operation
|
||||
|
||||
```js
|
||||
const beforeDeleteHook = ({
|
||||
req, // full express request
|
||||
id, // id of document to delete
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterDelete
|
||||
|
||||
Runs after `delete` operation
|
||||
|
||||
```js
|
||||
const afterDeleteHook = ({
|
||||
req, // full express request
|
||||
id, // id of document to delete
|
||||
doc, // deleted document
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeLogin
|
||||
|
||||
Runs before `login` operation
|
||||
|
||||
```js
|
||||
const beforeLoginHook = ({
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterLogin
|
||||
|
||||
Runs after `login` operation
|
||||
|
||||
```js
|
||||
const afterLoginHook = ({
|
||||
req, // full express request
|
||||
user, // user being logged in
|
||||
token, // user token
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterError
|
||||
|
||||
Runs after an error occurs
|
||||
|
||||
_TODO: example usage_
|
||||
|
||||
## Field Hooks
|
||||
|
||||
Field can be executed at any point in the modification or retrieval of a collection field.
|
||||
|
||||
Field Hooks available:
|
||||
|
||||
- `beforeValidate`
|
||||
- `beforeChange`
|
||||
- `afterChange`
|
||||
- `afterRead`
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
// Collection config
|
||||
module.exports = {
|
||||
slug: 'public-user',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
// Create and Update Operations
|
||||
beforeValidate: [(args) => {...}],
|
||||
beforeChange: [(args) => {...}],
|
||||
afterChange: [(args) => {...}],
|
||||
|
||||
// Read Operations
|
||||
afterRead: [(args) => {...}],
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
#### beforeValidate
|
||||
|
||||
Runs before the `create` and `update` operations.
|
||||
|
||||
```js
|
||||
const beforeValidateHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### beforeChange
|
||||
|
||||
Runs before the `create` and `update` operations, after validation.
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterChange
|
||||
|
||||
Runs after `create` and `update` operations
|
||||
|
||||
```js
|
||||
const afterChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
#### afterRead
|
||||
|
||||
Runs after result as been retrieved from database
|
||||
|
||||
```js
|
||||
const beforeChangeHook = ({
|
||||
value, // field value
|
||||
originalDoc, // original document
|
||||
data, // incoming document data
|
||||
operation, // name of the operation ie. 'create', 'update'
|
||||
req, // full express request
|
||||
}) => {...}
|
||||
```
|
||||
|
||||
## Hook Lifecycle
|
||||
|
||||
Hooks execute in the following order in relation to operations
|
||||
|
||||
`beforeOperation` is a special hook that runs _before all operations_.
|
||||
|
||||
### Create/Update Lifecycle
|
||||
|
||||
1. `beforeValidate` Field Hooks
|
||||
2. `beforeValidate` Collection Hooks
|
||||
3. Validate operation
|
||||
4. `beforeChange` Field Hooks
|
||||
5. `beforeChange` Collection Hooks
|
||||
6. Create/Update against database
|
||||
7. `afterChange` Field Hooks
|
||||
8. `afterChange` Collection Hooks
|
||||
|
||||
### Find Lifecycle
|
||||
|
||||
1. `beforeRead` Collection Hooks
|
||||
2. Find against database
|
||||
3. `afterRead` Field Hooks
|
||||
4. `afterRead` Collection Hooks
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
title: Hooks Examples
|
||||
label: Examples
|
||||
order: 20
|
||||
---
|
||||
|
||||
### Collection Hooks
|
||||
|
||||
#### Set createdBy and lastModifiedBy
|
||||
|
||||
```js
|
||||
beforeChange: [
|
||||
({ req, operation }) => {
|
||||
if (req.user && req.body) {
|
||||
if (operation === 'create') {
|
||||
req.body.createdBy = req.user.id;
|
||||
}
|
||||
req.body.lastModifiedBy = req.user.id;
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
_NOTE: createdBy and lastModifiedBy must be fields on the collection_
|
||||
|
||||
#### Use Local Payload API
|
||||
|
||||
Queries a separate collection and appends to returned result
|
||||
|
||||
```js
|
||||
afterRead: [
|
||||
async ({ req, doc }) => {
|
||||
const formattedData = { ...doc };
|
||||
const posts = await req.payload.find({
|
||||
collection: 'posts',
|
||||
});
|
||||
|
||||
formattedData.posts = posts;
|
||||
|
||||
return formattedData;
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
#### Read Request Header
|
||||
|
||||
```js
|
||||
beforeRead: [
|
||||
({ req }) => {
|
||||
if (req.headers.specialheader === 'special') {
|
||||
// Do something...
|
||||
}
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Field Hooks
|
||||
|
||||
#### Kebab-case Formatter
|
||||
|
||||
```js
|
||||
beforeValidate: [
|
||||
({ value }) => val.replace(/ /g, '-').toLowerCase();
|
||||
]
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: Server-Side Operations
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to run server-side operations.
|
||||
|
||||
- Access control
|
||||
- Providing a user
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: Production Deployment
|
||||
label: Deployment
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to deploy Payload here.
|
||||
|
||||
- DigitalOcean
|
||||
- If no file storage, Heroku
|
||||
- If file storage, need Heroku extensions
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: Production Security Measures
|
||||
label: Security
|
||||
order: 20
|
||||
---
|
||||
|
||||
- Rate limiting
|
||||
- GraphQL Complexity
|
||||
- Limits
|
||||
- Max Depth
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
title: REST API Overview
|
||||
label: Overview
|
||||
order: 10
|
||||
---
|
||||
|
||||
_TODO: Middleware_
|
||||
|
||||
<Banner type="info">
|
||||
The Payload REST API is created using properties in each collection's config
|
||||
</Banner>
|
||||
|
||||
Access to each collection is mounted on the API route using the collection's `slug` value. For example if a collection's slug is `users`, access to that collection will be mounted on `/api/users`.
|
||||
|
||||
Basic CRUD operations are automatically made available:
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Collections
|
||||
|
||||
| Method | Path | Description |
|
||||
| :------- | :---------------------- | :----------------------------------- |
|
||||
| `GET` | `/api/{collection}` | Get all {collection} documents |
|
||||
| `GET` | `/api/{collection}/:id` | Get a specific {collection} document |
|
||||
| `POST` | `/api/{collection}` | Create a {collection} document |
|
||||
| `DELETE` | `/api/{collection}/:id` | Delete a {collection} document |
|
||||
| `PUT` | `/api/{collection}/:id` | Update a {collection} document |
|
||||
|
||||
### Globals
|
||||
|
||||
| Method | Path | Description |
|
||||
| :------- | :---------------------- | :--------------------- |
|
||||
| `GET` | `/api/globals/{global}` | Get {global} |
|
||||
| `POST` | `/api/globals/{global}` | Create/Update {global} |
|
||||
| `DELETE` | `/api/globals/{global}` | Delete a {global} |
|
||||
|
||||
NOTE: The `/api` prefix can be customized in the [main Payload configuration](../Configuration/main)
|
||||
|
||||
## Responses
|
||||
|
||||
### Collections
|
||||
|
||||
All collection queries return metadata
|
||||
|
||||
#### Example Response
|
||||
|
||||
Collection Query
|
||||
|
||||
```js
|
||||
{
|
||||
// Document Array // highlight-line
|
||||
"docs": [
|
||||
{
|
||||
"title": "Page Title",
|
||||
"description": "Some description text",
|
||||
"priority": 1,
|
||||
"createdAt": "2020-10-17T01:19:29.858Z",
|
||||
"updatedAt": "2020-10-17T01:19:29.858Z",
|
||||
"id": "5f8a46a1dd05db75c3c64760"
|
||||
}
|
||||
],
|
||||
// Metadata // highlight-line
|
||||
"totalDocs": 6,
|
||||
"limit": 1,
|
||||
"totalPages": 6,
|
||||
"page": 1,
|
||||
"pagingCounter": 1,
|
||||
"hasPrevPage": false,
|
||||
"hasNextPage": true,
|
||||
"prevPage": null,
|
||||
"nextPage": 2
|
||||
}
|
||||
```
|
||||
|
||||
Documents queried by id and globals will return a single JSON document, no metadata
|
||||
|
||||
| Property | Description |
|
||||
| :------------ | :--------------------------------------------------------- |
|
||||
| docs | Array of documents in the {collection} |
|
||||
| totalDocs | Total available documents of {collection} |
|
||||
| limit | Limit query parameter. Defaults to `10`, can be configured |
|
||||
| totalPages | Total pages available, based upon the `limit` queried for |
|
||||
| page | Current page |
|
||||
| pagingCounter | `number` for the first doc on the current page |
|
||||
| hasPrevPage | `true/false` if previous page exists |
|
||||
| hasNextPage | `true/false` if next page exists |
|
||||
| prevPage | `number` of previous page. `null` if doesn't exist. |
|
||||
| nextPage | `number` of next page. `null` if doesn't exist. |
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| param | description | example |
|
||||
| :------ | :---------------------------------------------------------------------------------------- | :------------------------------- |
|
||||
| `limit` | Limits the number of documents returned | `?limit=3` |
|
||||
| `page` | Get specific page number | `?page=2` |
|
||||
| `sort` | Sort on a specific field. Use field name for ascending. Prefix with `-` for descending | `?sort=age`, `?sort=-age` |
|
||||
| `depth` | Controls how deep in JSON structure to return in the query. _Follows relationship fields_ | `?depth=3` |
|
||||
| `where` | Exposes many logical operators. _In-depth documentation below_ | `?where[property][equals]=value` |
|
||||
|
||||
### Where
|
||||
|
||||
Query on specific collection field values
|
||||
|
||||
| operator | description | example |
|
||||
| :------------------- | :-------------------------------------------------- | :------------------------------------------- |
|
||||
| `equals` | | `?where[property][equals]=value` |
|
||||
| `not_equals` | | `?where[property][not_equals]=value` |
|
||||
| `greater_than` | | `?where[property][greater_than]=value` |
|
||||
| `greater_than_equal` | | `?where[property][greater_than_equal]=value` |
|
||||
| `less_than` | | `?where[property][less_than]=value` |
|
||||
| `less_than_equal` | | `?where[property][less_than_equal]=value` |
|
||||
| `like` | Partial match against property | `?where[property][like]=value` |
|
||||
| `in` | Query for values in a comma-delimited list | `?where[property][in]=value1,value2` |
|
||||
| `not_in` | Query for values _not in_ in a comma-delimited list | `?where[property][not_in]=value1,value2` |
|
||||
| `exists` | Query for existence of a property | `?where[property][exists]=true` |
|
||||
|
||||
#### Complex Examples
|
||||
|
||||
_TODO: Complex Examples_
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: Upload Config
|
||||
label: Config
|
||||
order: 10
|
||||
---
|
||||
|
||||
Talk about how to configure uploads here.
|
||||
|
||||
Need to cover:
|
||||
|
||||
1. Can have multiple collections
|
||||
1. Static directory
|
||||
1. How to upload (multipart/form-data)
|
||||
1. Uploading is REST-only
|
||||
1. Access control
|
||||
1. Image sizes
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user