Compare commits
135 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee62ed6ebb | ||
|
|
0283039257 | ||
|
|
b546c7b655 | ||
|
|
a933eb7311 | ||
|
|
b5d65dd1ac | ||
|
|
c4ee623907 | ||
|
|
1cb1e5e8b3 | ||
|
|
e0699838e1 | ||
|
|
46f70d9df4 | ||
|
|
b7e2c59622 | ||
|
|
0cc7184023 | ||
|
|
e905675a05 | ||
|
|
4a20a63563 | ||
|
|
8d1fc6e8fb | ||
|
|
62744e79ac | ||
|
|
e8bed7b315 | ||
|
|
f2b8ddb299 | ||
|
|
ffd8ea516d | ||
|
|
3bf09703e9 | ||
|
|
c15d679b65 | ||
|
|
a422a0d568 | ||
|
|
edaeb1e29f | ||
|
|
6f35c356fe | ||
|
|
0b9397399a | ||
|
|
cdcc35ccdb | ||
|
|
442189ec48 | ||
|
|
5d1cc760c9 | ||
|
|
2f90683c7d | ||
|
|
3f5403a52a | ||
|
|
9bccdfd60a | ||
|
|
62666a9897 | ||
|
|
eb27b84854 | ||
|
|
c3480811d3 | ||
|
|
12ba820de4 | ||
|
|
95fcd13929 | ||
|
|
6141c5950b | ||
|
|
0040e1756c | ||
|
|
1ebd54b315 | ||
|
|
cdb2072a6d | ||
|
|
68553ff974 | ||
|
|
9a3bce1118 | ||
|
|
005befcbe2 | ||
|
|
e65b6478c9 | ||
|
|
a79e92a145 | ||
|
|
995f51d941 | ||
|
|
4d19e64961 | ||
|
|
31143599f6 | ||
|
|
f752804410 | ||
|
|
a18d4061ea | ||
|
|
449c16d28f | ||
|
|
d307d627ab | ||
|
|
075819964d | ||
|
|
1ec78a16f0 | ||
|
|
290ffd3287 | ||
|
|
3d89508ce3 | ||
|
|
b160686fff | ||
|
|
ba6ef6777f | ||
|
|
febd7f7073 | ||
|
|
695ef32d1e | ||
|
|
b5b2bb1907 | ||
|
|
6f5cf5d916 | ||
|
|
aaf3a39f7e | ||
|
|
5ef2951829 | ||
|
|
a943487fca | ||
|
|
3a941c7c8a | ||
|
|
354588898f | ||
|
|
ada9978a8c | ||
|
|
874279c530 | ||
|
|
7ed6634bc5 | ||
|
|
09a0ee3ab9 | ||
|
|
67acab2cd5 | ||
|
|
b3dc6cc811 | ||
|
|
5cd0c7ec7d | ||
|
|
cd592cb3a2 | ||
|
|
6d066c2ba4 | ||
|
|
1dc428823a | ||
|
|
c8da9b148c | ||
|
|
2021028d64 | ||
|
|
2ea56fe0f8 | ||
|
|
ea16119af7 | ||
|
|
97837f0708 | ||
|
|
e734d51760 | ||
|
|
5655266daa | ||
|
|
f9e5573c1e | ||
|
|
e823051a8e | ||
|
|
49df61d9ec | ||
|
|
4704c8db2a | ||
|
|
a64f37e014 | ||
|
|
55c6ce92b0 | ||
|
|
2ecbcee378 | ||
|
|
70f2e1698a | ||
|
|
8ba39aa5ca | ||
|
|
f8c79d2f84 | ||
|
|
128d72185d | ||
|
|
abc786d864 | ||
|
|
791fa68820 | ||
|
|
2796d2100f | ||
|
|
cbac62a36f | ||
|
|
b5afc62e14 | ||
|
|
0627272d6c | ||
|
|
51f1c8e7e8 | ||
|
|
09ad6e4280 | ||
|
|
c129c10f0f | ||
|
|
904ec0160e | ||
|
|
b2814eb67c | ||
|
|
c405e5958f | ||
|
|
a35979f74e | ||
|
|
863abc0e6b | ||
|
|
b9cf6c73a9 | ||
|
|
f2b3305cb0 | ||
|
|
b3e8ddf302 | ||
|
|
b6d4bc4d37 | ||
|
|
83ad453a89 | ||
|
|
4e6a7d489c | ||
|
|
a8d88b8238 | ||
|
|
5aa3283dc0 | ||
|
|
45844789f2 | ||
|
|
79975f48cf | ||
|
|
bba7cf37f8 | ||
|
|
1ae71a3d24 | ||
|
|
e83eb99436 | ||
|
|
f50e599684 | ||
|
|
7dab75d85e | ||
|
|
c45fbb9149 | ||
|
|
2c16c608ba | ||
|
|
c3f6c81dc6 | ||
|
|
a7b0f8ba36 | ||
|
|
014ee1a1b2 | ||
|
|
cf6da0186b | ||
|
|
18063bd256 | ||
|
|
76b3075369 | ||
|
|
3d63ce94bb | ||
|
|
f8a5103ed7 | ||
|
|
2bd53a06eb | ||
|
|
442518dbc9 |
45
.github/workflows/main.yml
vendored
45
.github/workflows/main.yml
vendored
@@ -196,6 +196,7 @@ jobs:
|
||||
- postgres-custom-schema
|
||||
- postgres-uuid
|
||||
- supabase
|
||||
- sqlite
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@@ -292,6 +293,7 @@ jobs:
|
||||
- access-control
|
||||
- admin__e2e__1
|
||||
- admin__e2e__2
|
||||
- admin-root
|
||||
- auth
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
@@ -390,6 +392,7 @@ jobs:
|
||||
# job-summary: true
|
||||
|
||||
app-build-with-packed:
|
||||
if: false # Disable until package resolution in tgzs can be figured out
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
@@ -546,3 +549,45 @@ jobs:
|
||||
steps:
|
||||
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
||||
run: exit 1
|
||||
|
||||
publish-canary:
|
||||
name: Publish Canary
|
||||
if: github.ref == 'refs/heads/beta'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- all-green
|
||||
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Load npm token
|
||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Canary release script
|
||||
# dry run hard-coded to true for testing and no npm token provided
|
||||
run: pnpm tsx ./scripts/publish-canary.ts
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
|
||||
16
.github/workflows/pr-title.yml
vendored
16
.github/workflows/pr-title.yml
vendored
@@ -38,6 +38,7 @@ jobs:
|
||||
db-\*
|
||||
db-mongodb
|
||||
db-postgres
|
||||
db-sqlite
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
@@ -101,3 +102,18 @@ jobs:
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
|
||||
label-pr-on-open:
|
||||
name: label-pr-on-open
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Tag with main branch with v2
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'main'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v2
|
||||
- name: Tag with beta branch with v3
|
||||
if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'beta'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v3
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,6 +22,10 @@ meta_shared.json
|
||||
# Ignore test directory media folder/files
|
||||
/media
|
||||
test/media
|
||||
*payloadtests.db
|
||||
*payloadtests.db-journal
|
||||
*payloadtests.db-shm
|
||||
*payloadtests.db-wal
|
||||
/versions
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
6
.idea/payload.iml
generated
6
.idea/payload.iml
generated
@@ -75,8 +75,12 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/drizzle/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/drizzle/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/dist" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -56,6 +56,20 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js fields-relationship",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields-Relationship",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js login-with-username",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Login-With-Username",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev plugin-cloud-storage",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -266,12 +266,10 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Label Components receive the following props:
|
||||
Custom Label Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`label`** | Label value provided in field, it can be used with i18n. |
|
||||
| **`required`** | The `admin.required` property defined in the [Field Config](../fields/overview). |
|
||||
| **`schemaPath`** | The path to the field in the schema. Similar to `path`, but without dynamic indices. |
|
||||
|
||||
<Banner type="success">
|
||||
@@ -279,6 +277,36 @@ All Label Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldLabelComponent,
|
||||
BlocksFieldLabelComponent,
|
||||
CheckboxFieldLabelComponent,
|
||||
CodeFieldLabelComponent,
|
||||
CollapsibleFieldLabelComponent,
|
||||
DateFieldLabelComponent,
|
||||
EmailFieldLabelComponent,
|
||||
GroupFieldLabelComponent,
|
||||
HiddenFieldLabelComponent,
|
||||
JSONFieldLabelComponent,
|
||||
NumberFieldLabelComponent,
|
||||
PointFieldLabelComponent,
|
||||
RadioFieldLabelComponent,
|
||||
RelationshipFieldLabelComponent,
|
||||
RichTextFieldLabelComponent,
|
||||
RowFieldLabelComponent,
|
||||
SelectFieldLabelComponent,
|
||||
TabsFieldLabelComponent,
|
||||
TextFieldLabelComponent,
|
||||
TextareaFieldLabelComponent,
|
||||
UploadFieldLabelComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Error Component
|
||||
|
||||
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
|
||||
@@ -301,7 +329,7 @@ export const myField: Field = {
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Error Components receive the following props:
|
||||
Custom Error Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ------------------------------------------------------------- |
|
||||
@@ -312,6 +340,36 @@ All Error Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldErrorComponent,
|
||||
BlocksFieldErrorComponent,
|
||||
CheckboxFieldErrorComponent,
|
||||
CodeFieldErrorComponent,
|
||||
CollapsibleFieldErrorComponent,
|
||||
DateFieldErrorComponent,
|
||||
EmailFieldErrorComponent,
|
||||
GroupFieldErrorComponent,
|
||||
HiddenFieldErrorComponent,
|
||||
JSONFieldErrorComponent,
|
||||
NumberFieldErrorComponent,
|
||||
PointFieldErrorComponent,
|
||||
RadioFieldErrorComponent,
|
||||
RelationshipFieldErrorComponent,
|
||||
RichTextFieldErrorComponent,
|
||||
RowFieldErrorComponent,
|
||||
SelectFieldErrorComponent,
|
||||
TabsFieldErrorComponent,
|
||||
TextFieldErrorComponent,
|
||||
TextareaFieldErrorComponent,
|
||||
UploadFieldErrorComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### The Description Property
|
||||
|
||||
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
|
||||
@@ -406,7 +464,7 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
|
||||
_For details on how to build a Custom Description, see [Building Custom Components](./components#building-custom-components)._
|
||||
|
||||
All Description Components receive the following props:
|
||||
Custom Description Components receive all [Field Component](#the-field-component) props, plus the following props:
|
||||
|
||||
| Property | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
@@ -417,6 +475,36 @@ All Description Components receive the following props:
|
||||
All [Custom Server Components](./components) receive the `payload` and `i18n` properties by default. See [Building Custom Components](./components#building-custom-components) for more details.
|
||||
</Banner>
|
||||
|
||||
#### TypeScript
|
||||
|
||||
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview). The convention is to append `DescriptionComponent` to the type of field, i.e. `TextFieldDescriptionComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
ArrayFieldDescriptionComponent,
|
||||
BlocksFieldDescriptionComponent,
|
||||
CheckboxFieldDescriptionComponent,
|
||||
CodeFieldDescriptionComponent,
|
||||
CollapsibleFieldDescriptionComponent,
|
||||
DateFieldDescriptionComponent,
|
||||
EmailFieldDescriptionComponent,
|
||||
GroupFieldDescriptionComponent,
|
||||
HiddenFieldDescriptionComponent,
|
||||
JSONFieldDescriptionComponent,
|
||||
NumberFieldDescriptionComponent,
|
||||
PointFieldDescriptionComponent,
|
||||
RadioFieldDescriptionComponent,
|
||||
RelationshipFieldDescriptionComponent,
|
||||
RichTextFieldDescriptionComponent,
|
||||
RowFieldDescriptionComponent,
|
||||
SelectFieldDescriptionComponent,
|
||||
TabsFieldDescriptionComponent,
|
||||
TextFieldDescriptionComponent,
|
||||
TextareaFieldDescriptionComponent,
|
||||
UploadFieldDescriptionComponent
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
### afterInput and beforeInput
|
||||
|
||||
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
|
||||
|
||||
@@ -86,19 +86,19 @@ const config = buildConfig({
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More details](../authentication/overview). |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](./components). |
|
||||
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
|
||||
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `disable` | If set to `true`, the entire Admin Panel will be disabled. |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
|
||||
| `meta` | Base metadata to use for the Admin Panel. Included properties are `titleSuffix`, `icons`, and `openGraph`. Can be overridden on a per Collection or per Global basis. |
|
||||
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
|
||||
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
|
||||
| Option | Description |
|
||||
|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](./components). |
|
||||
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
|
||||
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `disable` | If set to `true`, the entire Admin Panel will be disabled. |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
|
||||
| `meta` | Base metadata to use for the Admin Panel. Included properties are `titleSuffix`, `icons`, and `openGraph`. Can be overridden on a per Collection or per Global basis. |
|
||||
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
|
||||
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
@@ -169,19 +169,32 @@ The following options are available:
|
||||
|
||||
| Option | Default route | Description |
|
||||
| ------------------ | ----------------------- | ------------------------------------- |
|
||||
| `admin` | `/admin` | The Admin Panel itself. |
|
||||
| `admin` | `/admin` | The Admin Panel itself. |
|
||||
| `api` | `/api` | The [REST API](../rest-api/overview) base path. |
|
||||
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
|
||||
| `graphQLPlayground`| `/graphql-playground` | The GraphQL Playground. |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Warning:</strong>
|
||||
Changing Root-level Routes _after_ your project was generated will also require you to manually update the corresponding directories in your project. For example, changing `routes.admin` will require you to rename the `(payload)/admin` directory in your project to match the new route. [More details](#project-structure).
|
||||
</Banner>
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
You can easily add _new_ routes to the Admin Panel through the `endpoints` property of the Payload Config. See [Custom Endpoints](../rest-api/overview#custom-endpoints) for more information.
|
||||
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](./views).
|
||||
</Banner>
|
||||
|
||||
#### Customizing Root-level Routes
|
||||
|
||||
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
|
||||
|
||||
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
├─ (payload)/
|
||||
├── [[...segments]]/
|
||||
├──── ...
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
If you set Root-level Routes _before_ auto-generating the Admin Panel, your [Project Structure](#project-structure) will already be set up correctly.
|
||||
</Banner>
|
||||
|
||||
### Admin-level Routes
|
||||
|
||||
@@ -127,30 +127,27 @@ If set to `true`, users can log in with either their username or email address.
|
||||
|
||||
If set to `true`, an email address is required when creating a new user. If set to `false`, email is not required upon creation.
|
||||
|
||||
## Admin Auto-Login
|
||||
## Auto-Login
|
||||
|
||||
For testing and demo purposes you may want to skip forcing the admin user to login in order to access the [Admin Panel](../admin/overview). Typically, all users should be required to login to access the Admin Panel, however, you can speed up local development time by enabling auto-login.
|
||||
For testing and demo purposes you may want to skip forcing the user to login in order to access your application. Typically, all users should be required to login, however, you can speed up local development time by enabling auto-login.
|
||||
|
||||
To enable auto-login, set the `autoLogin` property in the [Admin Config](../configuration/admin):
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
// ...
|
||||
// highlight-start
|
||||
autoLogin:
|
||||
process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN === 'true'
|
||||
? {
|
||||
email: 'test@example.com',
|
||||
password: 'test',
|
||||
prefillOnly: true,
|
||||
}
|
||||
: false,
|
||||
// highlight-end
|
||||
},
|
||||
// highlight-start
|
||||
autoLogin:
|
||||
process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN === 'true'
|
||||
? {
|
||||
email: 'test@example.com',
|
||||
password: 'test',
|
||||
prefillOnly: true,
|
||||
}
|
||||
: false,
|
||||
// highlight-end
|
||||
})
|
||||
```
|
||||
|
||||
@@ -162,9 +159,10 @@ export default buildConfig({
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------------|-----------------------------------------------------------------------------------------------------------------|
|
||||
| **`username`** | The username of the user to login as |
|
||||
| **`email`** | The email address of the user to login as |
|
||||
| **`password`** | The password of the user to login as |
|
||||
| **`password`** | The password of the user to login as. This is only needed if `prefillOnly` is set to true |
|
||||
| **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. |
|
||||
|
||||
## Operations
|
||||
|
||||
@@ -64,38 +64,38 @@ export default buildConfig({
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||
| Option | Description |
|
||||
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
|
||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
|
||||
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
|
||||
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
|
||||
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
|
||||
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
|
||||
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
|
||||
| **`debug`** | Enable to expose more detailed error information. |
|
||||
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
|
||||
| **`rateLimit`** | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks, etc. [More details](../production/preventing-abuse#rate-limiting-requests). |
|
||||
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
|
||||
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
|
||||
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
|
||||
| **`secret`** \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
|
||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
|
||||
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
|
||||
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
|
||||
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
|
||||
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
|
||||
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
|
||||
| **`debug`** | Enable to expose more detailed error information. |
|
||||
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
|
||||
| **`rateLimit`** | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks, etc. [More details](../production/preventing-abuse#rate-limiting-requests). |
|
||||
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
|
||||
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
|
||||
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
|
||||
| **`secret`** \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export default buildConfig({
|
||||
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
|
||||
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
|
||||
|
||||
## Access to Mongoose models
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ export default buildConfig({
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
|
||||
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
## Access to Drizzle
|
||||
|
||||
|
||||
81
docs/database/sqlite.mdx
Normal file
81
docs/database/sqlite.mdx
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: SQLite
|
||||
label: SQLite
|
||||
order: 60
|
||||
desc: Payload supports SQLite through an officially supported Drizzle Database Adapter.
|
||||
keywords: SQLite, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
To use Payload with SQLite, install the package `@payloadcms/db-sqlite`. It leverages Drizzle ORM and `libSQL` to interact with a SQLite database that you provide.
|
||||
|
||||
It automatically manages changes to your database for you in development mode, and exposes a full suite of migration controls for you to leverage in order to keep other database environments in sync with your schema. DDL transformations are automatically generated.
|
||||
|
||||
To configure Payload to use SQLite, pass the `sqliteAdapter` to your Payload Config as follows:
|
||||
|
||||
```ts
|
||||
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||
|
||||
export default buildConfig({
|
||||
// Your config goes here
|
||||
collections: [
|
||||
// Collections go here
|
||||
],
|
||||
// Configure the SQLite adapter here
|
||||
db: sqliteAdapter({
|
||||
// SQLite-specific arguments go here.
|
||||
// `client.url` is required.
|
||||
client: {
|
||||
url: process.env.DATABASE_URL,
|
||||
authToken: process.env.DATABASE_AUTH_TOKEN,
|
||||
}
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
|
||||
|
||||
## Access to Drizzle
|
||||
|
||||
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
|
||||
|
||||
You can access Drizzle as follows:
|
||||
|
||||
```text
|
||||
payload.db.drizzle
|
||||
```
|
||||
|
||||
## Tables and relations
|
||||
|
||||
In addition to exposing Drizzle directly, all of the tables and Drizzle relations are exposed for you via the `payload.db` property as well.
|
||||
|
||||
- Tables - `payload.db.tables`
|
||||
- Relations - `payload.db.relations`
|
||||
|
||||
## Prototyping in development mode
|
||||
|
||||
Drizzle exposes two ways to work locally in development mode.
|
||||
|
||||
The first is [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push), which automatically pushes changes you make to your Payload Config (and therefore, Drizzle schema) to your database so you don't have to manually migrate every time you change your Payload Config. This only works in development mode, and should not be mixed with manually running [`migrate`](/docs/database/migrations) commands.
|
||||
|
||||
You will be warned if any changes that you make will entail data loss while in development mode. Push is enabled by default, but you can opt out if you'd like.
|
||||
|
||||
Alternatively, you can disable `push` and rely solely on migrations to keep your local database in sync with your Payload Config.
|
||||
|
||||
## Migration workflows
|
||||
|
||||
In SQLite, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
|
||||
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
@@ -68,3 +68,7 @@ The following functions can be used for managing transactions:
|
||||
`payload.db.beginTransaction` - Starts a new session and returns a transaction ID for use in other Payload Local API calls.
|
||||
`payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
|
||||
`payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
|
||||
|
||||
## Disabling Transactions
|
||||
|
||||
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
title: Array Field
|
||||
label: Array
|
||||
order: 20
|
||||
desc: Array fields are intended for sets of repeating fields, that you define. Learn how to use array fields, see examples and options.
|
||||
desc: Array Fields are intended for sets of repeating fields, that you define. Learn how to use Array Fields, see examples and options.
|
||||
keywords: array, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Array field type is used when you need to have a set of "repeating" fields. It stores an array
|
||||
of objects containing the fields that you define. Its uses can be simple or highly complex.
|
||||
</Banner>
|
||||
The Array Field is used when you need to have a set of "repeating" [Fields](./overview). It stores an array of objects containing fields that you define. These fields can be of any type, including other arrays to achieve infinitely nested structures.
|
||||
|
||||
Arrays are useful for many different types of content from simple to complex, such as:
|
||||
|
||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/array.png"
|
||||
@@ -18,13 +21,23 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of an Array field with two Rows"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To create an Array Field, set the `type` to `array` in your [Field Config](./overview):
|
||||
|
||||
- A "slider" with an image ([upload field](/docs/fields/upload)) and a caption ([text field](/docs/fields/text))
|
||||
- Navigational structures where editors can specify nav items containing pages ([relationship field](/docs/fields/relationship)), an "open in new tab" [checkbox field](/docs/fields/checkbox)
|
||||
- Event agenda "timeslots" where you need to specify start & end time ([date field](/docs/fields/date)), label ([text field](/docs/fields/text)), and Learn More page [relationship](/docs/fields/relationship)
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Config
|
||||
export const MyArrayField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'array',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -42,7 +55,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
@@ -50,9 +63,22 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Array Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyArrayField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Array Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -62,7 +88,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
In this example, we have an Array Field called `slider` that contains a set of fields for a simple image slider. Each row in the array has a `title`, `image`, and `caption`. We also customize the row label to display the title if it exists, or a default label if it doesn't.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
title: Blocks Field
|
||||
label: Blocks
|
||||
order: 30
|
||||
desc: The Blocks field type is a great layout build and can be used to construct any flexible content model. Learn how to use Block fields, see examples and options.
|
||||
desc: The Blocks Field is a great layout build and can be used to construct any flexible content model. Learn how to use Block Fields, see examples and options.
|
||||
keywords: blocks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Blocks field type is <strong>incredibly powerful</strong> and can be used as a{' '}
|
||||
<em>layout builder</em> as well as to define any other flexible content model you can think of. It
|
||||
stores an array of objects, where each object must match the shape of one of your provided block
|
||||
configs.
|
||||
</Banner>
|
||||
The Blocks Field is <strong>incredibly powerful</strong>, storing an array of objects based on the fields that your define, where each item in the array is a "block" with its own unique schema.
|
||||
|
||||
Blocks are a great way to create a flexible content model that can be used to build a wide variety of content types, including:
|
||||
|
||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/blocks.png"
|
||||
@@ -20,13 +21,23 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
||||
caption="Admin Panel screenshot of add Blocks drawer view"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To add a Blocks Field, set the `type` to `blocks` in your [Field Config](./overview):
|
||||
|
||||
- A layout builder tool that grants editors to design highly customizable page or post layouts. Blocks could include configs such as `Quote`, `CallToAction`, `Slider`, `Content`, `Gallery`, or others.
|
||||
- A form builder tool where available block configs might be `Text`, `Select`, or `Checkbox`.
|
||||
- Virtual event agenda "timeslots" where a timeslot could either be a `Break`, a `Presentation`, or a `BreakoutSession`.
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Field config
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -44,22 +55,35 @@ keywords: blocks, fields, config, configuration, documentation, Content Manageme
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Blocks Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ---------------------------------- |
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`isSortable`** | Disable order sorting by setting this value to `false` |
|
||||
|
||||
## Block configs
|
||||
## Block Configs
|
||||
|
||||
Blocks are defined as separate configs of their own.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Checkbox field types allow the developer to save a boolean value in the da
|
||||
keywords: checkbox, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>The Checkbox field type saves a boolean in the database.</Banner>
|
||||
The Checkbox Field saves a boolean in the database.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/checkbox.png"
|
||||
@@ -15,7 +15,18 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
|
||||
caption="Admin Panel screenshot of Checkbox field with Text field below"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Checkbox Field, set the `type` to `checkbox` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCheckboxField: Field = {
|
||||
// ...
|
||||
type: 'checkbox', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -30,7 +41,7 @@ keywords: checkbox, fields, config, configuration, documentation, Content Manage
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -38,7 +49,7 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
Here is an example of a Checkbox Field in a Collection:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
@@ -7,21 +7,27 @@ desc: The Code field type will store any string in the Database. Learn how to us
|
||||
keywords: code, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Code field type saves a string in the database, but provides the Admin Panel with a code
|
||||
editor styled interface.
|
||||
</Banner>
|
||||
The Code Field saves a string in the database, but provides the [Admin Panel](../admin/overview) with a code editor styled interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/code.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/code-dark.png"
|
||||
alt="Shows a Code field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a Code field"
|
||||
caption="This field is using the `monaco-react` editor syntax highlighting."
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
To add a Code Field, set the `type` to `code` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyBlocksField: Field = {
|
||||
// ...
|
||||
type: 'code', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -39,15 +45,28 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCodeField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Code Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: With the Collapsible field, you can place fields within a collapsible layo
|
||||
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Collapsible field is presentational-only and only affects the Admin Panel. By using it, you
|
||||
can place fields within a nice layout component that can be collapsed / expanded.
|
||||
</Banner>
|
||||
The Collapsible Field is presentational-only and only affects the Admin Panel. By using it, you can place fields within a nice layout component that can be collapsed / expanded.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/collapsible.png"
|
||||
@@ -18,20 +15,49 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Collapsible field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Collapsible Field, set the `type` to `collapsible` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCollapsibleField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'collapsible',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
|
||||
| **`fields`** \* | Array of field types to nest within this Collapsible. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyCollapsibleField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Collapsible Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------- |
|
||||
|
||||
@@ -6,21 +6,27 @@ desc: The Date field type stores a Date in the database. Learn how to use and cu
|
||||
keywords: date, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Date field type saves a Date in the database and provides the Admin Panel with a customizable
|
||||
time picker interface.
|
||||
</Banner>
|
||||
The Date Field saves a Date in the database and provides the [Admin Panel](../admin/overview) with a customizable time picker interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/date.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/date-dark.png"
|
||||
alt="Shows a Date field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a Date field"
|
||||
caption="This field is using the `react-datepicker` component for UI."
|
||||
/>
|
||||
|
||||
This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepicker) for the Admin Panel component.
|
||||
To add a Date Field, set the `type` to `date` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyDateField: Field = {
|
||||
// ...
|
||||
type: 'date', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -35,15 +41,28 @@ This field uses [`react-datepicker`](https://www.npmjs.com/package/react-datepic
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can customize the following fields that will adjust how the component displays in the Admin Panel via the `date` property.
|
||||
The customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyDateField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Date Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: The Email field enforces that the value provided is a valid email address.
|
||||
keywords: email, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>The Email field enforces that the value provided is a valid email address.</Banner>
|
||||
The Email Field enforces that the value provided is a valid email address.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/email.png"
|
||||
@@ -15,7 +15,18 @@ keywords: email, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of an Email field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To create an Email Field, set the `type` to `email` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyEmailField: Field = {
|
||||
// ...
|
||||
type: 'email', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -31,23 +42,33 @@ keywords: email, fields, config, configuration, documentation, Content Managemen
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string for the field.
|
||||
export const MyEmailField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Email Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Group field allows other fields to be nested under a common property.
|
||||
keywords: group, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Group field allows fields to be nested under a common property name. It also groups fields
|
||||
together visually in the Admin Panel.
|
||||
</Banner>
|
||||
The Group Field allows [Fields](./overview) to be nested under a common property name. It also groups fields together visually in the [Admin Panel](../admin/overview).
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/group.png"
|
||||
@@ -18,7 +15,23 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of a Group field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Group Field, set the `type` to `group` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyGroupField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'group',
|
||||
fields: [
|
||||
// ...
|
||||
],
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -32,20 +45,33 @@ keywords: group, fields, config, configuration, documentation, Content Managemen
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
|
||||
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Group allows for the following admin property:
|
||||
The customize the appearance and behavior of the Group Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`hideGutter`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
|
||||
export const MyGroupField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Group Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -7,21 +7,27 @@ desc: The JSON field type will store any string in the Database. Learn how to us
|
||||
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The JSON field type saves actual JSON in the database, which differs from the Code field that
|
||||
saves the value as a string in the database.
|
||||
</Banner>
|
||||
The JSON Field saves actual JSON in the database, which differs from the Code field that saves the value as a string in the database.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/json.png"
|
||||
srcDark="https://payloadcms.com/images/docs/fields/json-dark.png"
|
||||
alt="Shows a JSON field in the Payload Admin Panel"
|
||||
caption="Admin Panel screenshot of a JSON field"
|
||||
caption="This field is using the `monaco-react` editor syntax highlighting."
|
||||
/>
|
||||
|
||||
This field uses the `monaco-react` editor syntax highlighting.
|
||||
To add a JSON Field, set the `type` to `json` in your [Field Config](./overview):
|
||||
|
||||
## Config
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyJSONField: Field = {
|
||||
// ...
|
||||
type: 'json', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -38,15 +44,28 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
|
||||
The customize the appearance and behavior of the JSON Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyJSONField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The JSON Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Number fields store and validate numeric data. Learn how to use and format
|
||||
keywords: number, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Number field stores and validates numeric entry and supports additional numerical validation
|
||||
and formatting features.
|
||||
</Banner>
|
||||
The Number Field stores and validates numeric entry and supports additional numerical validation and formatting features.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/number.png"
|
||||
@@ -18,7 +15,18 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
caption="Admin Panel screenshot of a Number field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Number Field, set the `type` to `number` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyNumberField: Field = {
|
||||
// ...
|
||||
type: 'number', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -39,27 +47,34 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), this field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Number Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`step`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set a value for the number field to increment / decrement using browser controls.
|
||||
export const MyNumberField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`placeholder`**
|
||||
The Number Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to define a placeholder string for the field.
|
||||
|
||||
**`autoComplete`**
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`step`** | Set a value for the number field to increment / decrement using browser controls. |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -205,7 +205,9 @@ export const MyField: Field = {
|
||||
}
|
||||
```
|
||||
|
||||
Default values can be defined as a static string or a function that returns a string. Functions are called with the following arguments:
|
||||
Default values can be defined as a static value or a function that returns a value. When a `defaultValue` is defined statically, Payload's DB adapters will apply it to the database schema or models.
|
||||
|
||||
Functions can be written to make use of the following argument properties:
|
||||
|
||||
- `user` - the authenticated user object
|
||||
- `locale` - the currently selected locale string
|
||||
@@ -294,7 +296,7 @@ When using custom validation functions, Payload will use yours in place of the d
|
||||
To reuse default field validations, call them from within your custom validation function:
|
||||
|
||||
```ts
|
||||
import { text } from 'payload/fields/validations'
|
||||
import { text } from 'payload/shared'
|
||||
|
||||
const field: Field = {
|
||||
name: 'notBad',
|
||||
@@ -375,6 +377,8 @@ export const MyCollection: CollectionConfig = {
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong>
|
||||
The Custom ID Fields can only be of type [`Number`](./number) or [`Text`](./text).
|
||||
|
||||
Custom ID fields with type `text` must not contain `/` or `.` characters.
|
||||
</Banner>
|
||||
|
||||
## TypeScript
|
||||
|
||||
@@ -3,18 +3,10 @@ title: Point Field
|
||||
label: Point
|
||||
order: 110
|
||||
desc: The Point field type stores coordinates in the database. Learn how to use Point field for geolocation and geometry.
|
||||
|
||||
keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Point field type saves a pair of coordinates in the database and assigns an index for location
|
||||
related queries.
|
||||
</Banner>
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
The Point Field saves a pair of coordinates in the database and assigns an index for location related queries. The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/point.png"
|
||||
@@ -23,7 +15,21 @@ keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configurati
|
||||
caption="Admin Panel screenshot of a Point field"
|
||||
/>
|
||||
|
||||
The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [longitude, latitude] location.
|
||||
To add a Point Field, set the `type` to `point` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyPointField: Field = {
|
||||
// ...
|
||||
type: 'point', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
The Point Field is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
|
||||
## Config
|
||||
|
||||
@@ -41,16 +47,12 @@ The data structure in the database matches the GeoJSON structure to represent po
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong> The Point field type is currently only supported in MongoDB.
|
||||
</Banner>
|
||||
|
||||
## Example
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Radio field type allows for the selection of one value from a predefin
|
||||
keywords: radio, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Radio Group field type allows for the selection of one value from a predefined set of possible
|
||||
values and presents a radio group-style set of inputs to the Admin Panel.
|
||||
</Banner>
|
||||
The Radio Field allows for the selection of one value from a predefined set of possible values and presents a radio group-style set of inputs to the [Admin Panel](../admin/overview).
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/radio.png"
|
||||
@@ -18,7 +15,23 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
caption="Admin Panel screenshot of a Radio field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Radio Field, set the `type` to `radio` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRadioField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'radio',
|
||||
options: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -34,7 +47,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
@@ -50,13 +63,26 @@ _\* An asterisk denotes that a property is required._
|
||||
being used as a GraphQL enum.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Radio Group field type allows for the specification of the following `admin` properties:
|
||||
The customize the appearance and behavior of the Radio Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`layout`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
The `layout` property allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`.
|
||||
export const MyRadioField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The Radio Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`layout`** | Allows for the radio group to be styled as a horizonally or vertically distributed list. The default value is `horizontal`. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Relationship field provides the ability to relate documents together.
|
||||
keywords: relationship, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Relationship field is one of the most powerful fields Payload features. It provides for the
|
||||
ability to easily relate documents together.
|
||||
</Banner>
|
||||
The Relationship Field is one of the most powerful fields Payload features. It provides for the ability to easily relate documents together.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/relationship.png"
|
||||
@@ -18,13 +15,27 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
caption="Admin Panel screenshot of a Relationship field"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
The Relationship field is used in a variety of ways, including:
|
||||
|
||||
- To add `Product` documents to an `Order` document
|
||||
- To allow for an `Order` to feature a `placedBy` relationship to either an `Organization` or `User` collection
|
||||
- To assign `Category` documents to `Post` documents
|
||||
|
||||
## Config
|
||||
To add a Relationship Field, set the `type` to `relationship` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRelationshipField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'relationship',
|
||||
relationTo: 'products',
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -46,7 +57,7 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -54,30 +65,33 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
The [Depth](../queries/depth) parameter can be used to automatically populate
|
||||
related documents that are returned by the API.
|
||||
The [Depth](../queries/depth) parameter can be used to automatically populate related documents that are returned by the API.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Relationship field type also
|
||||
allows for the following admin-specific properties:
|
||||
The customize the appearance and behavior of the Relationship Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`isSortable`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany`
|
||||
is set to `true`).
|
||||
export const MyRelationshipField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`allowCreate`**
|
||||
The Relationship Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set to `false` if you'd like to disable the ability to create new documents from within the relationship field (hides
|
||||
the "Add new" button in the admin UI).
|
||||
| Property | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
|
||||
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
|
||||
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sortOptions) |
|
||||
|
||||
**`sortOptions`**
|
||||
|
||||
The `sortOptions` property allows you to define a default sorting order for the options within a Relationship field's
|
||||
dropdown. This can be particularly useful for ensuring that the most relevant options are presented first to the user.
|
||||
### Sort Options
|
||||
|
||||
You can specify `sortOptions` in two ways:
|
||||
|
||||
@@ -179,7 +193,7 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When a relationship field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default relationship field validation function imported from{' '}
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
## How the data is saved
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Rich Text field allows dynamic content to be written through the Admin
|
||||
keywords: rich text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Rich Text field is a powerful way to allow editors to write dynamic content. The content is
|
||||
saved as JSON in the database and can be converted into any format, including HTML, that you need.
|
||||
</Banner>
|
||||
The Rich Text Field is a powerful way to allow editors to write dynamic content. The content is saved as JSON in the database and can be converted into any format, including HTML, that you need.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/richtext.png"
|
||||
@@ -39,7 +36,7 @@ Right now, Payload is officially supporting two rich text editors:
|
||||
will allow you to apply your learnings elsewhere as well.
|
||||
</Banner>
|
||||
|
||||
## Config
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -53,29 +50,36 @@ Right now, Payload is officially supporting two rich text editors:
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`editor`** | Override the rich text editor specified in your base configuration for this field. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Rich Text editor allows for the following admin properties:
|
||||
The customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the text input.
|
||||
export const MyRichTextField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`hideGutter`**
|
||||
The Rich Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
|
||||
## Editor-specific options
|
||||
## Editor-specific Options
|
||||
|
||||
For a ton more editor-specific options, including how to build custom rich text elements directly into your editor, take a look at either the [Slate docs](/docs/rich-text/slate) or the [Lexical docs](/docs/rich-text/lexical) depending on which editor you're using.
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: With the Row field you can arrange fields next to each other in the Admin
|
||||
keywords: row, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Row field is presentational-only and only affects the Admin Panel. By using it, you can
|
||||
arrange fields next to each other horizontally.
|
||||
</Banner>
|
||||
The Row Field is presentational-only and only affects the [Admin Panel](../admin/overview). By using it, you can arrange [Fields](./overview) next to each other horizontally.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/row.png"
|
||||
@@ -18,12 +15,28 @@ keywords: row, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Row field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Row Field, set the `type` to `row` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyRowField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'row',
|
||||
fields: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`fields`** \* | Array of field types to nest within this Row. |
|
||||
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: The Select field provides a dropdown-style interface for choosing options
|
||||
keywords: select, multi-select, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Select field provides a dropdown-style interface for choosing options from a predefined list
|
||||
as an enumeration.
|
||||
</Banner>
|
||||
The Select Field provides a dropdown-style interface for choosing options from a predefined list as an enumeration.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/select.png"
|
||||
@@ -18,7 +15,23 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
caption="Admin Panel screenshot of a Select field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Select Field, set the `type` to `select` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MySelectField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'select',
|
||||
options: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,7 +49,7 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
@@ -46,24 +59,33 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
Option values should be strings that do not contain hyphens or special characters due to GraphQL
|
||||
enumeration naming constraints. Underscores are allowed. If you determine you need your option
|
||||
values to be non-strings or contain special characters, they will be formatted accordingly before
|
||||
being used as a GraphQL enum.
|
||||
</Banner>
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Select field type also allows for the following admin-specific properties:
|
||||
The customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`isClearable`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set to `true` if you'd like this field to be clearable within the Admin UI.
|
||||
export const MySelectField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`isSortable`**
|
||||
The Select Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`)
|
||||
| Property | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isClearable`** | Set to `true` if you'd like this field to be clearable within the Admin UI. |
|
||||
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`) |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,11 +6,7 @@ desc: The Tabs field is a great way to organize complex editing experiences into
|
||||
keywords: tabs, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Tabs field is presentational-only and only affects the Admin Panel (unless a tab is named). By
|
||||
using it, you can place fields within a nice layout component that separates certain sub-fields by
|
||||
a tabbed interface.
|
||||
</Banner>
|
||||
The Tabs Field is presentational-only and only affects the [Admin Panel](../admin/overview) (unless a tab is named). By using it, you can place fields within a nice layout component that separates certain sub-fields by a tabbed interface.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/tabs.png"
|
||||
@@ -19,12 +15,28 @@ keywords: tabs, fields, config, configuration, documentation, Content Management
|
||||
caption="Tabs field type used to separate Hero fields from Page Layout"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Tabs Field, set the `type` to `tabs` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTabsField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
// ...
|
||||
]
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
### Tab-specific Config
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Text field types simply save a string to the database and provide the Admi
|
||||
keywords: text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Text field type is one of the most commonly used fields. It saves a string to the database and
|
||||
provides the Admin Panel with a simple text input.
|
||||
</Banner>
|
||||
The Text Field is one of the most commonly used fields. It saves a string to the database and provides the [Admin Panel](../admin/overview) with a simple text input.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/text.png"
|
||||
@@ -18,7 +15,18 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
caption="Admin Panel screenshot of a Text field and read-only Text field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Text Field, set the `type` to `text` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTextField: Field = {
|
||||
// ...
|
||||
type: 'text', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,7 +44,7 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
|
||||
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
|
||||
@@ -45,21 +53,28 @@ keywords: text, fields, config, configuration, documentation, Content Management
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Text field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the text input.
|
||||
export const MyTextField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
| Option | Description |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string in the text input. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ desc: Textarea field types save a string to the database, similar to the Text fi
|
||||
keywords: textarea, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Textarea field is almost identical to the Text field but it features a slightly larger input
|
||||
that is better suited to edit longer text.
|
||||
</Banner>
|
||||
The Textarea Field is nearly identical to the [Text Field](./text) but it features a slightly larger input that is better suited to edit longer text.
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/textarea.png"
|
||||
@@ -18,7 +15,18 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
|
||||
caption="Admin Panel screenshot of a Textarea field and read-only Textarea field"
|
||||
/>
|
||||
|
||||
## Config
|
||||
To add a Textarea Field, set the `type` to `textarea` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyTextareaField: Field = {
|
||||
// ...
|
||||
type: 'textarea', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -36,27 +44,34 @@ keywords: textarea, fields, config, configuration, documentation, Content Manage
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin config
|
||||
## Admin Options
|
||||
|
||||
In addition to the default [field admin config](/docs/fields/overview#admin-config), the Textarea field type allows for the following `admin` properties:
|
||||
The customize the appearance and behavior of the Textarea Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
**`placeholder`**
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
Set this property to define a placeholder string in the textarea.
|
||||
export const MyTextareaField: Field = {
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**`autoComplete`**
|
||||
The Textarea Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
Set this property to a string that will be used for browser autocomplete.
|
||||
|
||||
**`rtl`**
|
||||
|
||||
Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction.
|
||||
| Option | Description |
|
||||
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string in the textarea. |
|
||||
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -6,31 +6,34 @@ desc: UI fields are purely presentational and allow developers to customize the
|
||||
keywords: custom field, react component, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The UI (user interface) field gives you a ton of power to add your own React components directly
|
||||
into the Admin Panel, nested directly within your other fields. It has absolutely no effect on the
|
||||
data of your documents. It is presentational-only.
|
||||
</Banner>
|
||||
The UI (user interface) Field gives you a ton of power to add your own React components directly into the [Admin Panel](../admin/overview), nested directly within your other fields. It has absolutely no effect on the data of your documents. It is presentational-only. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
|
||||
|
||||
This field is helpful if you need to build in custom functionality via React components within the Admin Panel. Think of it as a way for you to "plug in" your own React components directly within your other fields, so you can provide your editors with new controls exactly where you want them to go.
|
||||
|
||||
With this field, you can also inject custom `Cell` components that appear as additional columns within collections' List views.
|
||||
|
||||
**Example uses:**
|
||||
With the UI Field, you can:
|
||||
|
||||
- Add a custom message or block of text within the body of an Edit View to describe the purpose of surrounding fields
|
||||
- Add a "Refund" button to an Order's Edit View sidebar, which might make a fetch call to a custom `refund` endpoint
|
||||
- Add a "view page" button into a Pages List View to give editors a shortcut to view a page on the frontend of the site
|
||||
- Build a "clear cache" button or similar mechanism to manually clear caches of specific documents
|
||||
|
||||
## Config
|
||||
To add a UI Field, set the `type` to `ui` in your [Field Config](./overview):
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
export const MyUIField: Field = {
|
||||
// ...
|
||||
type: 'ui', // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | A unique identifier for this field. |
|
||||
| **`label`** | Human-readable label for this UI field. |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/docs/admin/components/#field-component) |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field-component) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field-component) |
|
||||
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
|
||||
@@ -6,18 +6,13 @@ desc: Upload fields will allow a file to be uploaded, only from a collection sup
|
||||
keywords: upload, images media, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner>
|
||||
The Upload field allows for the selection of a Document from a collection supporting Uploads, and
|
||||
formats the selection as a thumbnail in the Admin Panel.
|
||||
</Banner>
|
||||
The Upload Field allows for the selection of a Document from a Collection supporting [Uploads](../upload/overview), and formats the selection as a thumbnail in the Admin Panel.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
To use this field, you need to have a Collection configured to allow Uploads. For more
|
||||
information, [click here](/docs/upload/overview) to read about how to enable Uploads on a
|
||||
collection by collection basis.
|
||||
</Banner>
|
||||
Upload fields are useful for a variety of use cases, such as:
|
||||
|
||||
- To provide a `Page` with a featured image
|
||||
- To allow for a `Product` to deliver a downloadable asset like PDF or MP3
|
||||
- To give a layout building block the ability to feature a background image
|
||||
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/fields/upload.png"
|
||||
@@ -26,13 +21,26 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
caption="Admin Panel screenshot of an Upload field"
|
||||
/>
|
||||
|
||||
**Example uses:**
|
||||
To create an Upload Field, set the `type` to `upload` in your [Field Config](./overview):
|
||||
|
||||
- To provide a `Page` with a featured image
|
||||
- To allow for a `Product` to deliver a downloadable asset like PDF or MP3
|
||||
- To give a layout building block the ability to feature a background image
|
||||
```ts
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
## Config
|
||||
export const MyUploadField: Field = {
|
||||
// ...
|
||||
// highlight-start
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
To use the Upload Field, you must have a [Collection](../configuration/collections) configured to allow [Uploads](../upload/overview).
|
||||
</Banner>
|
||||
|
||||
## Config Options
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -49,9 +57,10 @@ keywords: upload, images media, fields, config, configuration, documentation, Co
|
||||
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`admin`** | Admin-specific configuration. [Admin Options](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
|
||||
@@ -115,5 +124,5 @@ You can learn more about writing queries [here](/docs/queries/overview).
|
||||
When an upload field has both <strong>filterOptions</strong> and a custom{' '}
|
||||
<strong>validate</strong> function, the api will not validate <strong>filterOptions</strong>{' '}
|
||||
unless you call the default upload field validation function imported from{' '}
|
||||
<strong>payload/fields/validations</strong> in your validate function.
|
||||
<strong>payload/shared</strong> in your validate function.
|
||||
</Banner>
|
||||
|
||||
@@ -79,6 +79,8 @@ app/
|
||||
├── // Your app files
|
||||
```
|
||||
|
||||
_For an exact reference of the `(payload)` directory, see [Project Structure](../admin/overview#project-structure)._
|
||||
|
||||
<Banner type="warning">
|
||||
You may need to copy all of your existing frontend files, including your existing root layout, into its own newly created [Route Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups), i.e. `(my-app)`.
|
||||
</Banner>
|
||||
|
||||
@@ -28,7 +28,7 @@ No matter what you're building, Payload will give you backend superpowers. It ca
|
||||
|
||||
### Open source - deploy anywhere, including Vercel
|
||||
|
||||
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js app folder.
|
||||
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node.js app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js application.
|
||||
|
||||
### Code-first and version controlled
|
||||
|
||||
|
||||
@@ -335,7 +335,6 @@ import {
|
||||
FieldMap,
|
||||
File,
|
||||
Form,
|
||||
FormFieldBase,
|
||||
FormLoadingOverlayToggle,
|
||||
FormSubmit,
|
||||
GenerateConfirmation,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Building Your Own Plugin
|
||||
label: Build Your Own
|
||||
order: 50
|
||||
order: 20
|
||||
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
@@ -159,36 +159,60 @@ export const seed = async (payload: Payload): Promise<void> => {
|
||||
|
||||
```
|
||||
|
||||
## Overview of the src folder
|
||||
## Building a Plugin
|
||||
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
|
||||
**index.ts**
|
||||
|
||||
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
|
||||
|
||||
**Plugin.ts**
|
||||
|
||||
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
|
||||
|
||||
```
|
||||
import type { Config } from 'payload'
|
||||
|
||||
export const samplePlugin =
|
||||
(pluginOptions: PluginTypes) =>
|
||||
(incomingConfig: Config): Config => {
|
||||
// create copy of incoming config
|
||||
let config = { ...incomingConfig }
|
||||
|
||||
// do something cool with the config here
|
||||
/**
|
||||
* This is where you could modify the
|
||||
* config based on the plugin options
|
||||
*/
|
||||
|
||||
// If you wanted to add a new collection:
|
||||
config.collections = [
|
||||
...(config.collections || []),
|
||||
newCollection,
|
||||
]
|
||||
|
||||
// If you wanted to add a new global:
|
||||
config.globals = [
|
||||
...(config.globals || []),
|
||||
newGlobal,
|
||||
]
|
||||
|
||||
/**
|
||||
* If you wanted to add a new field to a collection:
|
||||
*
|
||||
* 1. Loop over collections
|
||||
* 2. Find the collection you want to add the field to
|
||||
* 3. Add the field to the collection
|
||||
*/
|
||||
|
||||
// If you wanted to add to the onInit:
|
||||
config.onInit = async payload => {
|
||||
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
|
||||
// Add additional onInit code here
|
||||
}
|
||||
|
||||
// Finally, return the modified config
|
||||
return config
|
||||
}
|
||||
```
|
||||
|
||||
1. First, you need to receive the existing Payload Config along with any plugin options.
|
||||
2. Then set the variable `config` to be equal to a copy of the existing config.
|
||||
3. From here, you can extend the config however you like!
|
||||
4. Finally, return the config and you're all set.
|
||||
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
|
||||
|
||||
## Spread Syntax
|
||||
|
||||
### Spread syntax
|
||||
|
||||
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
|
||||
|
||||
@@ -206,7 +230,7 @@ config.collections = [
|
||||
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular Payload Config.
|
||||
|
||||
This same logic is applied to other properties like admin, globals, hooks:
|
||||
This same logic is applied to other array and object like properties such as admin, globals and hooks:
|
||||
|
||||
```
|
||||
config.globals = [
|
||||
@@ -220,7 +244,10 @@ config.hooks = {
|
||||
}
|
||||
```
|
||||
|
||||
Some properties will be slightly different to extend, for instance the `onInit` property:
|
||||
### Extending functions
|
||||
Function properties cannot use spread syntax. The way to extend them is to execute the existing function if it exists and then run your additional functionality.
|
||||
|
||||
Here is an example extending the `onInit` property:
|
||||
|
||||
```
|
||||
config.onInit = async payload => {
|
||||
@@ -231,10 +258,6 @@ config.onInit = async payload => {
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to add to the `onInit`, you must include the async/await. We don't use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
|
||||
|
||||
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
|
||||
|
||||
## Types
|
||||
|
||||
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Form Builder Plugin
|
||||
label: Form Builder
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Easily build and manage forms from the Admin Panel. Send dynamic, personalized emails and even accept and process payments.
|
||||
keywords: plugins, plugin, form, forms, form builder
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Nested Docs Plugin
|
||||
label: Nested Docs
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Nested documents in a parent, child, and sibling relationship.
|
||||
keywords: plugins, nested, documents, parent, child, sibling, relationship
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Redirects Plugin
|
||||
label: Redirects
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Automatically create redirects for your Payload application
|
||||
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Search Plugin
|
||||
label: Search
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Generates records of your documents that are extremely fast to search on.
|
||||
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Sentry Plugin
|
||||
label: Sentry
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Integrate Sentry error tracking into your Payload application
|
||||
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: SEO Plugin
|
||||
label: SEO
|
||||
order: 20
|
||||
order: 30
|
||||
desc: Manage SEO metadata from your Payload admin
|
||||
keywords: plugins, seo, meta, search, engine, ranking, google
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Stripe Plugin
|
||||
label: Stripe
|
||||
order: 20
|
||||
order: 40
|
||||
desc: Easily accept payments with Stripe
|
||||
keywords: plugins, stripe, payments, ecommerce
|
||||
---
|
||||
|
||||
@@ -599,7 +599,7 @@ export const Orders: CollectionConfig = {
|
||||
{
|
||||
path: '/:id/tracking',
|
||||
method: 'get',
|
||||
handler: (req) => {
|
||||
handler: async (req) => {
|
||||
const tracking = await getTrackingInfo(req.params.id)
|
||||
|
||||
if (!tracking) {
|
||||
@@ -614,7 +614,7 @@ export const Orders: CollectionConfig = {
|
||||
{
|
||||
path: '/:id/tracking',
|
||||
method: 'post',
|
||||
handler: (req) => {
|
||||
handler: async (req) => {
|
||||
// `data` is not automatically appended to the request
|
||||
// if you would like to read the body of the request
|
||||
// you can use `data = await req.json()`
|
||||
@@ -654,7 +654,7 @@ import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
|
||||
{
|
||||
path: '/:id/tracking',
|
||||
method: 'post',
|
||||
handler: (req) => {
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
await req.payload.update({
|
||||
collection: 'tracking',
|
||||
@@ -680,7 +680,7 @@ import { addLocalesToRequestFromData } from '@payloadcms/next/utilities'
|
||||
{
|
||||
path: '/:id/tracking',
|
||||
method: 'post',
|
||||
handler: (req) => {
|
||||
handler: async (req) => {
|
||||
await addLocalesToRequestFromData(req)
|
||||
// you now can access req.locale & req.fallbackLocale
|
||||
return Response.json({ message: 'success' })
|
||||
|
||||
@@ -94,6 +94,7 @@ _An asterisk denotes that an option is required._
|
||||
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
@@ -104,6 +105,7 @@ _An asterisk denotes that an option is required._
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
|
||||
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
|
||||
| **`withMetadata`** | If specified, appends metadata to the output image file. Accepts a boolean or a function that receives `metadata` and `req`, returning a boolean. |
|
||||
|
||||
|
||||
### Payload-wide Upload Options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Payload Multi-Tenant Example
|
||||
# Payload Multi-Tenant Example (Single Domain)
|
||||
|
||||
This example demonstrates how to achieve a multi-tenancy in [Payload](https://github.com/payloadcms/payload) on a single domain. Tenants are separated by a `Tenants` collection.
|
||||
|
||||
@@ -6,12 +6,16 @@ This example demonstrates how to achieve a multi-tenancy in [Payload](https://gi
|
||||
|
||||
To spin up this example locally, follow these steps:
|
||||
|
||||
1. First clone the repo
|
||||
2. `cd YOUR_PROJECT_REPO && cp .env.example .env`
|
||||
3. `pnpm i && pnpm dev`
|
||||
4. run `yarn seed` to seed the database
|
||||
5. open `http://localhost:3000/admin` to access the admin panel
|
||||
6. Login with email `demo@payloadcms.com` and password `demo`
|
||||
1. Clone this repo
|
||||
1. `cd` into this directory and run `pnpm i --ignore-workspace`\*, `yarn`, or `npm install`
|
||||
|
||||
> \*If you are running using pnpm within the Payload Monorepo, the `--ignore-workspace` flag is needed so that pnpm generates a lockfile in this example's directory despite the fact that one exists in root.
|
||||
|
||||
1. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
|
||||
- Press `y` when prompted to seed the database
|
||||
1. `open http://localhost:3000` to access the home page
|
||||
1. `open http://localhost:3000/admin` to access the admin panel
|
||||
- Login with email `demo@payloadcms.com` and password `demo`
|
||||
|
||||
## How it works
|
||||
|
||||
|
||||
8
examples/multi-tenant-single-domain/next.config.mjs
Normal file
8
examples/multi-tenant-single-domain/next.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig)
|
||||
@@ -1,21 +1,18 @@
|
||||
{
|
||||
"name": "multi-tenant-single-domain",
|
||||
"description": "An example of a multi tenant application, using a single domain",
|
||||
"version": "1.0.0",
|
||||
"description": "An example of a multi tenant application, using a single domain",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation next dev --turbo",
|
||||
"_dev": "cross-env NODE_OPTIONS=--no-deprecation next dev",
|
||||
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
|
||||
"generate:types": "payload generate:types",
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation && pnpm seed && next dev --turbo",
|
||||
"generate:schema": "payload-graphql generate:schema",
|
||||
"seed": "tsx ./scripts/seed.ts"
|
||||
"generate:types": "payload generate:types",
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
"seed": "npm run payload migrate:fresh",
|
||||
"start": "cross-env NODE_OPTIONS=--no-deprecation next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/db-mongodb": "3.0.0-beta.58",
|
||||
@@ -42,6 +39,9 @@
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "5.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@types/react": "npm:types-react@19.0.0-beta.2",
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
/**
|
||||
* This is an example of a standalone script that loads in the Payload config
|
||||
* and uses the Payload Local API to query the database.
|
||||
*/
|
||||
|
||||
process.env.PAYLOAD_DROP_DATABASE = 'true'
|
||||
|
||||
import type { Payload, RequiredDataFromCollectionSlug } from 'payload'
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import { importConfig } from 'payload/node'
|
||||
|
||||
async function findOrCreateTenant({ data, payload }: { data: any; payload: Payload }) {
|
||||
const tenantsQuery = await payload.find({
|
||||
collection: 'tenants',
|
||||
where: {
|
||||
slug: {
|
||||
equals: data.slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (tenantsQuery.docs?.[0]) return tenantsQuery.docs[0]
|
||||
|
||||
return payload.create({
|
||||
collection: 'tenants',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
async function findOrCreateUser({
|
||||
data,
|
||||
payload,
|
||||
}: {
|
||||
data: RequiredDataFromCollectionSlug<'users'>
|
||||
payload: Payload
|
||||
}) {
|
||||
const usersQuery = await payload.find({
|
||||
collection: 'users',
|
||||
where: {
|
||||
email: {
|
||||
equals: data.email,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (usersQuery.docs?.[0]) return usersQuery.docs[0]
|
||||
|
||||
return payload.create({
|
||||
collection: 'users',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
async function findOrCreatePage({ data, payload }: { data: any; payload: Payload }) {
|
||||
const pagesQuery = await payload.find({
|
||||
collection: 'pages',
|
||||
where: {
|
||||
slug: {
|
||||
equals: data.slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (pagesQuery.docs?.[0]) return pagesQuery.docs[0]
|
||||
|
||||
return payload.create({
|
||||
collection: 'pages',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const awaitedConfig = await importConfig('../src/payload.config.ts')
|
||||
const payload = await getPayload({ config: awaitedConfig })
|
||||
|
||||
const tenant1 = await findOrCreateTenant({
|
||||
data: {
|
||||
name: 'Tenant 1',
|
||||
slug: 'tenant-1',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
const tenant2 = await findOrCreateTenant({
|
||||
data: {
|
||||
name: 'Tenant 2',
|
||||
slug: 'tenant-2',
|
||||
public: true,
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
const tenant3 = await findOrCreateTenant({
|
||||
data: {
|
||||
name: 'Tenant 3',
|
||||
slug: 'tenant-3',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreateUser({
|
||||
data: {
|
||||
email: 'demo@payloadcms.com',
|
||||
password: 'demo',
|
||||
roles: ['super-admin'],
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreateUser({
|
||||
data: {
|
||||
email: 'tenant1@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant1.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant1',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreateUser({
|
||||
data: {
|
||||
email: 'tenant2@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant2.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant2',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreateUser({
|
||||
data: {
|
||||
email: 'tenant3@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant3.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant3',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreateUser({
|
||||
data: {
|
||||
email: 'multi-admin@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant1.id,
|
||||
},
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant2.id,
|
||||
},
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant3.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant3',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreatePage({
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant1.id,
|
||||
title: 'Page for Tenant 1',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreatePage({
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant2.id,
|
||||
title: 'Page for Tenant 2',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
await findOrCreatePage({
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant3.id,
|
||||
title: 'Page for Tenant 3',
|
||||
},
|
||||
payload,
|
||||
})
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
run().catch(console.error)
|
||||
@@ -2,7 +2,7 @@ import type { Where } from 'payload'
|
||||
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { headers as getHeaders } from 'next/headers'
|
||||
import { notFound, redirect } from 'next/navigation'
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default {};
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { PayloadRequest } from "payload";
|
||||
|
||||
export const isPayloadAdminPanel = (req: PayloadRequest) => {
|
||||
return req.headers.has('referer') && req.headers.get('referer')?.startsWith(`${process.env.NEXT_PUBLIC_SERVER_URL}${req.payload.config.routes.admin}`)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import type { Access } from 'payload'
|
||||
|
||||
import { parseCookies } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const filterByTenantRead: Access = (args) => {
|
||||
const req = args.req
|
||||
@@ -2,7 +2,7 @@ import type { FieldHook } from 'payload'
|
||||
|
||||
import { ValidationError } from 'payload'
|
||||
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const ensureUniqueSlug: FieldHook = async ({ data, originalDoc, req, value }) => {
|
||||
// if value is unchanged, skip validation
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { tenantField } from '../../fields/TenantField/index.js'
|
||||
import { isPayloadAdminPanel } from '../../utilities/isPayloadAdminPanel.js'
|
||||
import { canMutatePage, filterByTenantRead } from './access/byTenant.js'
|
||||
import { externalReadAccess } from './access/externalReadAccess.js'
|
||||
import { ensureUniqueSlug } from './hooks/ensureUniqueSlug.js'
|
||||
import { tenantField } from '../../fields/TenantField'
|
||||
import { isPayloadAdminPanel } from '../../utilities/isPayloadAdminPanel'
|
||||
import { canMutatePage, filterByTenantRead } from './access/byTenant'
|
||||
import { externalReadAccess } from './access/externalReadAccess'
|
||||
import { ensureUniqueSlug } from './hooks/ensureUniqueSlug'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
@@ -2,8 +2,8 @@ import type { Access } from 'payload'
|
||||
|
||||
import { parseCookies } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const filterByTenantRead: Access = (args) => {
|
||||
const req = args.req
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Access } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const tenantRead: Access = (args) => {
|
||||
const req = args.req
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../access/isSuperAdmin.js'
|
||||
import { canMutateTenant, filterByTenantRead } from './access/byTenant.js'
|
||||
import { isSuperAdmin } from '../../access/isSuperAdmin'
|
||||
import { canMutateTenant, filterByTenantRead } from './access/byTenant'
|
||||
|
||||
export const Tenants: CollectionConfig = {
|
||||
slug: 'tenants',
|
||||
@@ -2,8 +2,8 @@ import type { Access } from 'payload'
|
||||
|
||||
import type { User } from '../../../../payload-types'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const createAccess: Access<User> = (args) => {
|
||||
const { req } = args
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Access } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { isAccessingSelf } from './isAccessingSelf.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { isAccessingSelf } from './isAccessingSelf'
|
||||
|
||||
export const isSuperAdminOrSelf: Access = (args) => isSuperAdmin(args) || isAccessingSelf(args)
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { User } from '@/payload-types'
|
||||
import type { Access, Where } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '@/cms/access/isSuperAdmin'
|
||||
import { getTenantAdminTenantAccessIDs } from '@/cms/utilities/getTenantAccessIDs'
|
||||
import { parseCookies } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const readAccess: Access<User> = (args) => {
|
||||
const { req } = args
|
||||
if (!req?.user) return false
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Access } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const updateAndDeleteAccess: Access = (args) => {
|
||||
const { req } = args
|
||||
@@ -2,7 +2,7 @@ import type { FieldHook } from 'payload'
|
||||
|
||||
import { ValidationError } from 'payload'
|
||||
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const ensureUniqueUsername: FieldHook = async ({ data, originalDoc, req, value }) => {
|
||||
// if value is unchanged, skip validation
|
||||
@@ -2,12 +2,12 @@ import type { CollectionConfig } from 'payload'
|
||||
|
||||
import type { User } from '../../../payload-types'
|
||||
|
||||
import { getTenantAdminTenantAccessIDs } from '../../utilities/getTenantAccessIDs.js'
|
||||
import { createAccess } from './access/create.js'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../utilities/getTenantAccessIDs'
|
||||
import { createAccess } from './access/create'
|
||||
import { readAccess } from './access/read'
|
||||
import { updateAndDeleteAccess } from './access/updateAndDelete.js'
|
||||
import { externalUsersLogin } from './endpoints/externalUsersLogin.js'
|
||||
import { ensureUniqueUsername } from './hooks/ensureUniqueUsername.js'
|
||||
import { updateAndDeleteAccess } from './access/updateAndDelete'
|
||||
import { externalUsersLogin } from './endpoints/externalUsersLogin'
|
||||
import { ensureUniqueUsername } from './hooks/ensureUniqueUsername'
|
||||
|
||||
const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cookies as getCookies } from 'next/headers'
|
||||
import React from 'react'
|
||||
|
||||
import { TenantSelector } from './index.client.js'
|
||||
import { TenantSelector } from './index.client'
|
||||
|
||||
export const TenantSelectorRSC = () => {
|
||||
const cookies = getCookies()
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FieldAccess } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const tenantFieldUpdate: FieldAccess = (args) => {
|
||||
const tenantIDs = getTenantAccessIDs(args.req.user)
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FieldHook } from 'payload'
|
||||
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs.js'
|
||||
import { getTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
export const autofillTenant: FieldHook = ({ req, value }) => {
|
||||
// If there is no value,
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Field } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../access/isSuperAdmin.js'
|
||||
import { tenantFieldUpdate } from './access/update.js'
|
||||
import { TenantFieldComponent } from './components/Field.js'
|
||||
import { autofillTenant } from './hooks/autofillTenant.js'
|
||||
import { isSuperAdmin } from '../../access/isSuperAdmin'
|
||||
import { tenantFieldUpdate } from './access/update'
|
||||
import { TenantFieldComponent } from './components/Field'
|
||||
import { autofillTenant } from './hooks/autofillTenant'
|
||||
|
||||
export const tenantField: Field = {
|
||||
name: 'tenant',
|
||||
131
examples/multi-tenant-single-domain/src/migrations/seed.ts
Normal file
131
examples/multi-tenant-single-domain/src/migrations/seed.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import type { MigrateUpArgs } from '@payloadcms/db-mongodb'
|
||||
|
||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'demo@payloadcms.com',
|
||||
password: 'demo',
|
||||
roles: ['super-admin'],
|
||||
},
|
||||
})
|
||||
|
||||
const tenant1 = await payload.create({
|
||||
collection: 'tenants',
|
||||
data: {
|
||||
name: 'Tenant 1',
|
||||
slug: 'tenant-1',
|
||||
},
|
||||
})
|
||||
|
||||
const tenant2 = await payload.create({
|
||||
collection: 'tenants',
|
||||
data: {
|
||||
name: 'Tenant 2',
|
||||
slug: 'tenant-2',
|
||||
},
|
||||
})
|
||||
|
||||
const tenant3 = await payload.create({
|
||||
collection: 'tenants',
|
||||
data: {
|
||||
name: 'Tenant 3',
|
||||
slug: 'tenant-3',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'tenant1@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant1.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant1',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'tenant2@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant2.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant2',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'tenant3@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant3.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant3',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'multi-admin@payloadcms.com',
|
||||
password: 'test',
|
||||
tenants: [
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant1.id,
|
||||
},
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant2.id,
|
||||
},
|
||||
{
|
||||
roles: ['tenant-admin'],
|
||||
tenant: tenant3.id,
|
||||
},
|
||||
],
|
||||
username: 'tenant3',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant1.id,
|
||||
title: 'Page for Tenant 1',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant2.id,
|
||||
title: 'Page for Tenant 2',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
slug: 'home',
|
||||
tenant: tenant3.id,
|
||||
title: 'Page for Tenant 3',
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
@@ -131,4 +131,4 @@ export interface Auth {
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import path from 'path'
|
||||
import { buildConfig } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { Pages } from './cms/collections/Pages/index.js'
|
||||
import { Tenants } from './cms/collections/Tenants/index.js'
|
||||
import Users from './cms/collections/Users/index.js'
|
||||
import { TenantSelectorRSC } from './cms/components/TenantSelector/index.js'
|
||||
import { Pages } from './collections/Pages'
|
||||
import { Tenants } from './collections/Tenants'
|
||||
import Users from './collections/Users'
|
||||
import { TenantSelectorRSC } from './components/TenantSelector'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { PayloadRequest } from 'payload'
|
||||
|
||||
export const isPayloadAdminPanel = (req: PayloadRequest) => {
|
||||
return (
|
||||
req.headers.has('referer') &&
|
||||
req.headers
|
||||
.get('referer')
|
||||
?.startsWith(`${process.env.NEXT_PUBLIC_SERVER_URL}${req.payload.config.routes.admin}`)
|
||||
)
|
||||
}
|
||||
@@ -27,10 +27,10 @@
|
||||
"./src/*"
|
||||
],
|
||||
"@payload-config": [
|
||||
"./src/payload.config.ts"
|
||||
"src/payload.config.ts"
|
||||
],
|
||||
"@payload-types": [
|
||||
"./src/payload-types.ts"
|
||||
"src/payload-types.ts"
|
||||
]
|
||||
},
|
||||
"target": "ES2017"
|
||||
|
||||
23
package.json
23
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.67",
|
||||
"version": "3.0.0-beta.77",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -15,6 +15,8 @@
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:db-sqlite": "turbo build --filter db-sqlite",
|
||||
"build:drizzle": "turbo build --filter drizzle",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:email-resend": "turbo build --filter email-resend",
|
||||
"build:eslint-config": "turbo build --filter eslint-config",
|
||||
@@ -22,6 +24,7 @@
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
"build:live-preview-react": "turbo build --filter live-preview-react",
|
||||
"build:live-preview-vue": "turbo build --filter live-preview-vue",
|
||||
"build:next": "turbo build --filter next",
|
||||
"build:payload": "turbo build --filter payload",
|
||||
"build:plugin-cloud": "turbo build --filter plugin-cloud",
|
||||
@@ -92,7 +95,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.53",
|
||||
"@libsql/client": "0.6.2",
|
||||
"@next/bundle-analyzer": "15.0.0-canary.104",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
@@ -115,8 +119,7 @@
|
||||
"create-payload-app": "workspace:*",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-kit": "0.20.14-1f2c838",
|
||||
"drizzle-orm": "0.29.4",
|
||||
"drizzle-orm": "0.32.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
"form-data": "3.0.1",
|
||||
@@ -128,15 +131,15 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "15.0.0-canary.53",
|
||||
"next": "15.0.0-canary.104",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.43.0",
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "3.3.2",
|
||||
"prompts": "2.4.2",
|
||||
"react": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0-rc-6230622a1a-20240610",
|
||||
"react": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
|
||||
"rimraf": "3.0.2",
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "0.32.6",
|
||||
@@ -147,11 +150,11 @@
|
||||
"tempy": "1.0.1",
|
||||
"tsx": "4.16.2",
|
||||
"turbo": "^1.13.3",
|
||||
"typescript": "5.5.3"
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.67",
|
||||
"version": "3.0.0-beta.77",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -50,6 +50,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"@swc/core": "^1.6.13",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"comment-json": "^4.2.3",
|
||||
|
||||
@@ -10,22 +10,23 @@ export async function getPackageManager(args: {
|
||||
const { cliArgs, projectDir } = args
|
||||
|
||||
try {
|
||||
// Check for yarn.lock, package-lock.json, or pnpm-lock.yaml
|
||||
// Check for flag or lockfile
|
||||
let detected: PackageManager = 'npm'
|
||||
if (
|
||||
cliArgs?.['--use-pnpm'] ||
|
||||
fse.existsSync(`${projectDir}/pnpm-lock.yaml`) ||
|
||||
(await commandExists('pnpm'))
|
||||
) {
|
||||
if (cliArgs?.['--use-pnpm'] || fse.existsSync(`${projectDir}/pnpm-lock.yaml`)) {
|
||||
detected = 'pnpm'
|
||||
} else if (
|
||||
cliArgs?.['--use-yarn'] ||
|
||||
fse.existsSync(`${projectDir}/yarn.lock`) ||
|
||||
(await commandExists('yarn'))
|
||||
) {
|
||||
} else if (cliArgs?.['--use-yarn'] || fse.existsSync(`${projectDir}/yarn.lock`)) {
|
||||
detected = 'yarn'
|
||||
} else if (cliArgs?.['--use-npm'] || fse.existsSync(`${projectDir}/package-lock.json`)) {
|
||||
detected = 'npm'
|
||||
} else {
|
||||
// Otherwise check for existing commands
|
||||
if (await commandExists('pnpm')) {
|
||||
detected = 'pnpm'
|
||||
} else if (await commandExists('yarn')) {
|
||||
detected = 'yarn'
|
||||
} else {
|
||||
detected = 'npm'
|
||||
}
|
||||
}
|
||||
|
||||
return detected
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const installSpinner = p.spinner()
|
||||
installSpinner.start('Installing Payload and dependencies...')
|
||||
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
const configurationResult = await installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
@@ -143,15 +143,16 @@ async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean)
|
||||
}
|
||||
}
|
||||
|
||||
function installAndConfigurePayload(
|
||||
async function installAndConfigurePayload(
|
||||
args: {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
} & InitNextArgs,
|
||||
):
|
||||
): Promise<
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
| { payloadConfigPath?: string; reason: string; success: false }
|
||||
> {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
@@ -212,7 +213,7 @@ function installAndConfigurePayload(
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
await wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
@@ -239,25 +240,63 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
|
||||
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
|
||||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
|
||||
|
||||
// Match next.config.js, next.config.ts, next.config.mjs, next.config.cjs
|
||||
const nextConfigPath: string | undefined = (
|
||||
await globby('next.config.*js', { absolute: true, cwd: projectDir })
|
||||
await globby('next.config.(\\w)?(t|j)s', { absolute: true, cwd: projectDir })
|
||||
)?.[0]
|
||||
|
||||
if (!nextConfigPath || nextConfigPath.length === 0) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath: undefined,
|
||||
nextVersion: null,
|
||||
}
|
||||
}
|
||||
|
||||
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
|
||||
// Check if Next.js version is new enough
|
||||
let nextVersion = null
|
||||
if (packageObj.dependencies?.next) {
|
||||
nextVersion = packageObj.dependencies.next
|
||||
// Match versions using regex matching groups
|
||||
const versionMatch = /(?<major>\d+)/.exec(nextVersion)
|
||||
if (!versionMatch) {
|
||||
p.log.warn(`Could not determine Next.js version from ${nextVersion}`)
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
|
||||
const { major } = versionMatch.groups as { major: string }
|
||||
const majorVersion = parseInt(major)
|
||||
if (majorVersion < 15) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion: false,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isSupportedNextVersion = true
|
||||
|
||||
// Check if Payload already installed
|
||||
if (packageObj.dependencies?.payload) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isPayloadInstalled: true,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,14 +319,27 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
|
||||
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
|
||||
: false
|
||||
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
|
||||
return {
|
||||
hasTopLevelLayout,
|
||||
isSrcDir,
|
||||
isSupportedNextVersion,
|
||||
nextAppDir,
|
||||
nextConfigPath,
|
||||
nextConfigType: configType,
|
||||
nextVersion,
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectType(args: {
|
||||
nextConfigPath: string
|
||||
packageObj: Record<string, unknown>
|
||||
}): 'cjs' | 'esm' {
|
||||
}): NextConfigType {
|
||||
const { nextConfigPath, packageObj } = args
|
||||
|
||||
if (nextConfigPath.endsWith('.ts')) {
|
||||
return 'ts'
|
||||
}
|
||||
|
||||
if (nextConfigPath.endsWith('.mjs')) {
|
||||
return 'esm'
|
||||
}
|
||||
|
||||
@@ -29,9 +29,22 @@ const postgresReplacement: DbAdapterReplacement = {
|
||||
packageName: '@payloadcms/db-postgres',
|
||||
}
|
||||
|
||||
const sqliteReplacement: DbAdapterReplacement = {
|
||||
configReplacement: (envName = 'DATABASE_URI') => [
|
||||
' db: sqliteAdapter({',
|
||||
' client: {',
|
||||
` url: process.env.${envName} || '',`,
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
importReplacement: "import { sqliteAdapter } from '@payloadcms/db-sqlite'",
|
||||
packageName: '@payloadcms/db-sqlite',
|
||||
}
|
||||
|
||||
export const dbReplacements: Record<DbType, DbAdapterReplacement> = {
|
||||
mongodb: mongodbReplacement,
|
||||
postgres: postgresReplacement,
|
||||
sqlite: sqliteReplacement,
|
||||
}
|
||||
|
||||
type StorageAdapterReplacement = {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { CliArgs, DbDetails, DbType } from '../types.js'
|
||||
|
||||
type DbChoice = {
|
||||
dbConnectionPrefix: `${string}/`
|
||||
dbConnectionSuffix?: string
|
||||
title: string
|
||||
value: DbType
|
||||
}
|
||||
@@ -20,6 +21,12 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
sqlite: {
|
||||
dbConnectionPrefix: 'file:./',
|
||||
dbConnectionSuffix: '.db',
|
||||
title: 'SQLite (beta)',
|
||||
value: 'sqlite',
|
||||
},
|
||||
}
|
||||
|
||||
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
|
||||
@@ -37,10 +44,10 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
|
||||
initialValue: 'mongodb',
|
||||
message: `Select a database`,
|
||||
options: [
|
||||
{ label: 'MongoDB', value: 'mongodb' },
|
||||
{ label: 'Postgres', value: 'postgres' },
|
||||
],
|
||||
options: Object.values(dbChoiceRecord).map((dbChoice) => ({
|
||||
label: dbChoice.title,
|
||||
value: dbChoice.value,
|
||||
})),
|
||||
})
|
||||
if (p.isCancel(dbType)) process.exit(0)
|
||||
}
|
||||
@@ -50,7 +57,7 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
let dbUri: string | symbol | undefined = undefined
|
||||
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
|
||||
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
|
||||
}`
|
||||
}${dbChoice.dbConnectionSuffix || ''}`
|
||||
|
||||
if (args['--db-accept-recommended']) {
|
||||
dbUri = initialDbUri
|
||||
|
||||
@@ -74,9 +74,11 @@ export async function updatePayloadInProject(
|
||||
info('Payload packages updated successfully.')
|
||||
|
||||
info(`Updating Payload Next.js files...`)
|
||||
const templateFilesPath = dirname.endsWith('dist')
|
||||
? path.resolve(dirname, '../..', 'dist/template')
|
||||
: path.resolve(dirname, '../../../../templates/blank-3.0')
|
||||
|
||||
const templateFilesPath =
|
||||
process.env.JEST_WORKER_ID !== undefined
|
||||
? path.resolve(dirname, '../../../../templates/blank-3.0')
|
||||
: path.resolve(dirname, '../..', 'dist/template')
|
||||
|
||||
const templateSrcDir = path.resolve(templateFilesPath, 'src/app/(payload)')
|
||||
|
||||
|
||||
@@ -3,6 +3,35 @@ import { jest } from '@jest/globals'
|
||||
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
|
||||
const tsConfigs = {
|
||||
defaultNextConfig: `import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {};
|
||||
export default nextConfig;`,
|
||||
|
||||
nextConfigExportNamedDefault: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithFunc: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {};
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigWithSpread: `import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
@@ -52,27 +81,66 @@ module.exports = nextConfig;
|
||||
}
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
describe('ts', () => {
|
||||
const configType = 'ts'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent: modifiedConfigContent2 } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent2).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
tsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
describe('esm', () => {
|
||||
const configType = 'esm'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
@@ -80,8 +148,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
@@ -90,10 +158,10 @@ describe('parseAndInsertWithPayload', () => {
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
it('should give warning with a named export as default', async () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent, success } = await parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -109,39 +177,39 @@ describe('parseAndInsertWithPayload', () => {
|
||||
describe('cjs', () => {
|
||||
const configType = 'cjs'
|
||||
const requireStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the default next config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse anonymous default config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse anonymous default config', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.anonConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload({})')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a multi-lined function', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n {2}nextConfig\n\)\)/)
|
||||
})
|
||||
it('should parse the config with a named export as default', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a named export as default', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
@@ -149,8 +217,8 @@ describe('parseAndInsertWithPayload', () => {
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
it('should parse the config with a spread', async () => {
|
||||
const { modifiedConfigContent } = await parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import type { Program } from 'esprima-next'
|
||||
import type { ExportDefaultExpression, ModuleItem } from '@swc/core'
|
||||
|
||||
import swc from '@swc/core'
|
||||
import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import type { NextConfigType } from '../types.js'
|
||||
|
||||
import { log, warning } from '../utils/log.js'
|
||||
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
cjs: `const { withPayload } = require("@payloadcms/next/withPayload");`,
|
||||
esm: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
ts: `import { withPayload } from "@payloadcms/next/withPayload";`,
|
||||
}
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
export const wrapNextConfig = (args: {
|
||||
export const wrapNextConfig = async (args: {
|
||||
nextConfigPath: string
|
||||
nextConfigType: NextConfigType
|
||||
}) => {
|
||||
const { nextConfigPath, nextConfigType: configType } = args
|
||||
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
const { modifiedConfigContent: newConfig, success } = await parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
@@ -34,113 +36,142 @@ export const wrapNextConfig = (args: {
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
export function parseAndModifyConfigContent(
|
||||
export async function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
): Promise<{ modifiedConfigContent: string; success: boolean }> {
|
||||
content = withPayloadStatement[configType] + '\n' + content
|
||||
|
||||
let ast: Program | undefined
|
||||
try {
|
||||
ast = parseModule(content, { loc: true })
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
console.log({ configType, content })
|
||||
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
if (configType === 'cjs' || configType === 'esm') {
|
||||
try {
|
||||
const ast = parseModule(content, { loc: true })
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
} else if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
} else if (configType === 'ts') {
|
||||
const { moduleItems, parseOffset } = await compileTypeScriptFileToAST(content)
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
} else if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
const exportDefaultDeclaration = moduleItems.find(
|
||||
(m) =>
|
||||
m.type === 'ExportDefaultExpression' &&
|
||||
(m.expression.type === 'Identifier' || m.expression.type === 'CallExpression'),
|
||||
) as ExportDefaultExpression | undefined
|
||||
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
if (exportDefaultDeclaration) {
|
||||
if (!('span' in exportDefaultDeclaration.expression)) {
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
})
|
||||
}
|
||||
|
||||
const modifiedConfigContent = insertBeforeAndAfterSWC(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
exportDefaultDeclaration.expression.span,
|
||||
parseOffset,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
return Promise.resolve({
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
${chalk.bold(`Please manually wrap your existing Next config with the withPayload function. Here is an example:`)}
|
||||
|
||||
${withPayloadStatement[configType]}
|
||||
|
||||
@@ -148,7 +179,7 @@ function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
${configType === 'cjs' ? 'module.exports = withPayload(nextConfig)' : 'export default withPayload(nextConfig)'}
|
||||
|
||||
`
|
||||
|
||||
@@ -186,7 +217,7 @@ type Loc = {
|
||||
start: { column: number; line: number }
|
||||
}
|
||||
|
||||
function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
function insertBeforeAndAfter(content: string, loc: Loc): string {
|
||||
const { end, start } = loc
|
||||
const lines = content.split('\n')
|
||||
|
||||
@@ -205,3 +236,57 @@ function insertBeforeAndAfter(content: string, loc: Loc) {
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function insertBeforeAndAfterSWC(
|
||||
content: string,
|
||||
span: ModuleItem['span'],
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests. Defaults to 0 otherwise.
|
||||
*
|
||||
* @see compileTypeScriptFileToAST
|
||||
*/
|
||||
parseOffset: number,
|
||||
): string {
|
||||
const { end: preOffsetEnd, start: preOffsetStart } = span
|
||||
|
||||
const start = preOffsetStart - parseOffset
|
||||
const end = preOffsetEnd - parseOffset
|
||||
|
||||
const insert = (pos: number, text: string): string => {
|
||||
return content.slice(0, pos) + text + content.slice(pos)
|
||||
}
|
||||
|
||||
// insert ) after end
|
||||
content = insert(end - 1, ')')
|
||||
// insert withPayload before start
|
||||
content = insert(start - 1, 'withPayload(')
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile typescript to AST using the swc compiler
|
||||
*/
|
||||
async function compileTypeScriptFileToAST(
|
||||
fileContent: string,
|
||||
): Promise<{ moduleItems: ModuleItem[]; parseOffset: number }> {
|
||||
let parseOffset = 0
|
||||
|
||||
/**
|
||||
* WARNING: This is ONLY for unit tests.
|
||||
*
|
||||
* Multiple instances of swc DO NOT reset the .span.end value.
|
||||
* During unit tests, the .spawn.end value is read and accounted for.
|
||||
*
|
||||
* https://github.com/swc-project/swc/issues/1366
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
parseOffset = (await swc.parse('')).span.end
|
||||
}
|
||||
|
||||
const module = await swc.parse(fileContent, {
|
||||
syntax: 'typescript',
|
||||
})
|
||||
|
||||
return { moduleItems: module.body, parseOffset }
|
||||
}
|
||||
|
||||
@@ -85,7 +85,22 @@ export class Main {
|
||||
|
||||
// Detect if inside Next.js project
|
||||
const nextAppDetails = await getNextAppDetails(process.cwd())
|
||||
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails
|
||||
const {
|
||||
hasTopLevelLayout,
|
||||
isPayloadInstalled,
|
||||
isSupportedNextVersion,
|
||||
nextAppDir,
|
||||
nextConfigPath,
|
||||
nextVersion,
|
||||
} = nextAppDetails
|
||||
|
||||
if (nextConfigPath && !isSupportedNextVersion) {
|
||||
p.log.warn(
|
||||
`Next.js v${nextVersion} is unsupported. Next.js >= 15 is required to use Payload.`,
|
||||
)
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Upgrade Payload in existing project
|
||||
if (isPayloadInstalled && nextConfigPath) {
|
||||
|
||||
@@ -57,7 +57,7 @@ interface Template {
|
||||
|
||||
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
|
||||
|
||||
export type DbType = 'mongodb' | 'postgres'
|
||||
export type DbType = 'mongodb' | 'postgres' | 'sqlite'
|
||||
|
||||
export type DbDetails = {
|
||||
dbUri: string
|
||||
@@ -70,11 +70,13 @@ export type NextAppDetails = {
|
||||
hasTopLevelLayout: boolean
|
||||
isPayloadInstalled?: boolean
|
||||
isSrcDir: boolean
|
||||
isSupportedNextVersion: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
nextConfigType?: NextConfigType
|
||||
nextVersion: null | string
|
||||
}
|
||||
|
||||
export type NextConfigType = 'cjs' | 'esm'
|
||||
export type NextConfigType = 'cjs' | 'esm' | 'ts'
|
||||
|
||||
export type StorageAdapterType = 'localDisk' | 'payloadCloud' | 'vercelBlobStorage'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.67",
|
||||
"version": "3.0.0-beta.77",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bson-objectid": "2.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
|
||||
@@ -54,6 +54,10 @@ export const connect: Connect = async function connect(
|
||||
this.payload.logger.info('---- DROPPED DATABASE ----')
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && this.prodMigrations) {
|
||||
await this.migrate({ migrations: this.prodMigrations })
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.payload.logger.error(`Error: cannot connect to MongoDB. Details: ${err.message}`, err)
|
||||
|
||||
@@ -54,6 +54,14 @@ export const find: Find = async function find(
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
|
||||
@@ -64,7 +64,7 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
forceCountFn: hasNearConstraint,
|
||||
lean: true,
|
||||
leanWithId: true,
|
||||
offset: skip,
|
||||
limit,
|
||||
options,
|
||||
page,
|
||||
pagination,
|
||||
@@ -72,6 +72,14 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
useEstimatedCount,
|
||||
}
|
||||
|
||||
if (this.collation) {
|
||||
const defaultLocale = 'en'
|
||||
paginationOptions.collation = {
|
||||
locale: locale && locale !== 'all' && locale !== '*' ? locale : defaultLocale,
|
||||
...this.collation,
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user