Compare commits
1 Commits
v3.0.0
...
beta-issue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce5d1fef6 |
1
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
1
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
@@ -39,7 +39,6 @@ body:
|
||||
- 'db-postgres'
|
||||
- 'db-sqlite'
|
||||
- 'db-vercel-postgres'
|
||||
- 'email-nodemailer'
|
||||
- 'plugin: cloud'
|
||||
- 'plugin: cloud-storage'
|
||||
- 'plugin: form-builder'
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -8,7 +8,7 @@ updates:
|
||||
- /.github/workflows
|
||||
- /.github/actions/* # Not working until resolved: https://github.com/dependabot/dependabot-core/issues/6345
|
||||
- /.github/actions/setup
|
||||
target-branch: main
|
||||
target-branch: beta
|
||||
schedule:
|
||||
interval: monthly
|
||||
timezone: America/Detroit
|
||||
@@ -20,7 +20,7 @@ updates:
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
target-branch: main
|
||||
target-branch: beta
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: sunday
|
||||
@@ -53,7 +53,7 @@ updates:
|
||||
# Only bump patch versions for 2.x
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
target-branch: 2.x
|
||||
target-branch: main
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: sunday
|
||||
|
||||
4
.github/reproduction-guide.md
vendored
4
.github/reproduction-guide.md
vendored
@@ -40,7 +40,7 @@ There are a couple ways run integration tests:
|
||||
|
||||
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/github/int-debug.png" />
|
||||
|
||||
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
|
||||
|
||||
@@ -57,7 +57,7 @@ The easiest way to run E2E tests is to install
|
||||
|
||||
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
|
||||
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/github/e2e-debug.png" />
|
||||
|
||||
#### Notes
|
||||
|
||||
|
||||
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
|
||||
concurrency:
|
||||
# <workflow_name>-<branch_name>-<true || commit_sha if branch is protected>
|
||||
@@ -283,7 +284,7 @@ jobs:
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
name: e2e-${{ matrix.suite }}
|
||||
name: ${{ matrix.suite }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -293,7 +294,6 @@ jobs:
|
||||
- access-control
|
||||
- admin__e2e__1
|
||||
- admin__e2e__2
|
||||
- admin__e2e__3
|
||||
- admin-root
|
||||
- auth
|
||||
- field-error-states
|
||||
@@ -414,10 +414,6 @@ jobs:
|
||||
- template: with-vercel-postgres
|
||||
database: postgres
|
||||
|
||||
# Re-enable once PG conncection is figured out
|
||||
# - template: with-vercel-website
|
||||
# database: postgres
|
||||
|
||||
name: ${{ matrix.template }}-${{ matrix.database }}
|
||||
|
||||
env:
|
||||
@@ -529,7 +525,7 @@ jobs:
|
||||
publish-canary:
|
||||
name: Publish Canary
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.all-green.result == 'success' && github.ref_name == 'main' }}
|
||||
if: ${{ needs.all-green.result == 'success' && github.ref_name == 'beta' }}
|
||||
needs:
|
||||
- all-green
|
||||
|
||||
@@ -537,4 +533,4 @@ jobs:
|
||||
# debug github.ref output
|
||||
- run: |
|
||||
echo github.ref: ${{ github.ref }}
|
||||
echo isV3: ${{ github.ref == 'refs/heads/main' }}
|
||||
echo isBeta: ${{ github.ref == 'refs/heads/beta' }}
|
||||
|
||||
81
.github/workflows/post-release.yml
vendored
81
.github/workflows/post-release.yml
vendored
@@ -5,26 +5,16 @@ on:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to process (optional)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.6.0
|
||||
PNPM_VERSION: 9.7.1
|
||||
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
|
||||
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
|
||||
|
||||
jobs:
|
||||
post_release:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Only needed if debugging on a branch other than default
|
||||
# ref: ${{ github.event.release.target_commitish || github.ref }}
|
||||
- uses: ./.github/actions/release-commenter
|
||||
continue-on-error: true
|
||||
env:
|
||||
@@ -38,70 +28,3 @@ jobs:
|
||||
|
||||
comment-template: |
|
||||
🚀 This is included in version {release_link}
|
||||
|
||||
update_templates:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Update template lockfiles and migrations
|
||||
run: pnpm script:gen-templates
|
||||
|
||||
- name: Determine Release Tag
|
||||
id: determine_tag
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.tag }}" != "" ]; then
|
||||
echo "Using tag from input: ${{ github.event.inputs.tag }}"
|
||||
echo "release_tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Using tag from release event: ${{ github.event.release.tag_name }}"
|
||||
echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Commit and push changes
|
||||
id: commit
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -ex
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
export BRANCH_NAME=chore/templates-${{ steps.determine_tag.outputs.release_tag }}
|
||||
git checkout -b $BRANCH_NAME
|
||||
git add -A
|
||||
|
||||
# If no files have changed, exit early with success
|
||||
git diff --cached --quiet --exit-code && exit 0
|
||||
|
||||
git commit -m "chore(templates): bump lockfiles after ${{ steps.determine_tag.outputs.release_tag }}"
|
||||
git push origin $BRANCH_NAME
|
||||
echo "committed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Debug Branches
|
||||
run: |
|
||||
echo "Target Commitish: ${{ github.event.release.target_commitish }}"
|
||||
echo "Branch: ${{ steps.commit.outputs.branch }}"
|
||||
echo "Ref: ${{ github.ref }}"
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
if: steps.commit.outputs.committed == 'true'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: 'area: templates'
|
||||
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||||
commit-message: 'Automated update after release'
|
||||
branch: ${{ steps.commit.outputs.branch }}
|
||||
base: ${{ github.event_name != 'workflow_dispatch' && github.event.release.target_commitish || github.ref }}
|
||||
title: 'chore(templates): bump lockfiles after ${{ steps.determine_tag.outputs.release_tag }}'
|
||||
body: 'Automated bump of template lockfiles after release ${{ steps.determine_tag.outputs.release_tag }}'
|
||||
|
||||
12
.github/workflows/pr-title.yml
vendored
12
.github/workflows/pr-title.yml
vendored
@@ -109,13 +109,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Tag with 2.x branch with v2
|
||||
if: github.event.pull_request.base.ref == '2.x'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v2
|
||||
- name: Tag with main branch with v3
|
||||
- name: Tag with main branch with v2
|
||||
if: 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.pull_request.base.ref == 'beta'
|
||||
uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: v3
|
||||
|
||||
2
.github/workflows/release-canary.yml
vendored
2
.github/workflows/release-canary.yml
vendored
@@ -3,7 +3,7 @@ name: release-canary
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
|
||||
env:
|
||||
NODE_VERSION: 22.6.0
|
||||
|
||||
@@ -45,7 +45,7 @@ There are a couple ways to do this:
|
||||
|
||||
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/src/admin/assets/images/github/int-debug.png" />
|
||||
|
||||
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
|
||||
|
||||
@@ -62,7 +62,7 @@ The easiest way to run E2E tests is to install
|
||||
|
||||
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
|
||||
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
|
||||
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/src/admin/assets/images/github/e2e-debug.png" />
|
||||
|
||||
#### Notes
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/main/packages/payload/src/assets/images/github-banner-nextjs-native.jpg" alt="Payload headless CMS Admin panel built with React" /></a>
|
||||
<a href="https://payloadcms.com"><img width="100%" src="https://github.com/payloadcms/payload/blob/beta/packages/payload/src/assets/images/github-banner-nextjs-native.jpg" alt="Payload headless CMS Admin panel built with React" /></a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ You can also override Payload's built-in [CSS Variables](https://developer.mozil
|
||||
|
||||
The following variables are defined and can be overridden:
|
||||
|
||||
- [Breakpoints](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/queries.scss)
|
||||
- [Colors](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/colors.scss)
|
||||
- [Breakpoints](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/queries.scss)
|
||||
- [Colors](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/colors.scss)
|
||||
- Base color shades (white to black by default)
|
||||
- Success / warning / error color shades
|
||||
- Theme-specific colors (background, input background, text color, etc.)
|
||||
- Elevation colors (used to determine how "bright" something should be when compared to the background)
|
||||
- [Sizing](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss/app.scss)
|
||||
- [Sizing](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/app.scss)
|
||||
- Horizontal gutter
|
||||
- Transition speeds
|
||||
- Font sizes
|
||||
|
||||
@@ -121,11 +121,6 @@ All Description Functions receive the following arguments:
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
|
||||
|
||||
<Banner type="info">
|
||||
<strong>Note:</strong>
|
||||
If you need to subscribe to live updates within your form, use a Description Component instead. [More details](#description).
|
||||
</Banner>
|
||||
|
||||
## Conditional Logic
|
||||
|
||||
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes three arguments:
|
||||
|
||||
@@ -21,11 +21,10 @@ To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import type { TextFieldClientComponent } from 'payload'
|
||||
import { useField } from '@payloadcms/ui'
|
||||
|
||||
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
|
||||
const { value, setValue } = useField({ path }) // highlight-line
|
||||
const CustomTextField: React.FC = () => {
|
||||
const { value, setValue, path } = useField() // highlight-line
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -177,7 +177,7 @@ The following options are available:
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](./views).
|
||||
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
|
||||
@@ -237,7 +237,7 @@ The following options are available:
|
||||
|
||||
## I18n
|
||||
|
||||
The Payload Admin Panel is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/main/packages/translations). Languages are automatically detected based on the user's browser and used by the Admin Panel to display all text in that language. If no language was detected, or if the user's language is not yet supported, English will be chosen. Users can easily specify their language by selecting one from their account page. See [I18n](../configuration/i18n) for more information.
|
||||
The Payload Admin Panel is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/beta/packages/translations). Languages are automatically detected based on the user's browser and used by the Admin Panel to display all text in that language. If no language was detected, or if the user's language is not yet supported, English will be chosen. Users can easily specify their language by selecting one from their account page. See [I18n](../configuration/i18n) for more information.
|
||||
|
||||
## Light and Dark Modes
|
||||
|
||||
|
||||
@@ -350,8 +350,6 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
### Default Props
|
||||
|
||||
Your Custom Views will be provided with the following props:
|
||||
|
||||
| Prop | Description |
|
||||
@@ -361,7 +359,6 @@ Your Custom Views will be provided with the following props:
|
||||
| **`importMap`** | The import map object. |
|
||||
| **`params`** | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
|
||||
| **`searchParams`** | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
|
||||
| **`doc`** | The document being edited. Only available in Document Views. [More details](#document-views). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Reminder:</strong>
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Manage and customize internationalization support in your CMS editor exper
|
||||
keywords: internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The [Admin Panel](../admin/overview) is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/main/packages/translations). With I18n, editors can navigate the interface and read API error messages in their preferred language. This is similar to [Localization](./localization), but instead of managing translations for the data itself, you are managing translations for your application's interface.
|
||||
The [Admin Panel](../admin/overview) is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/beta/packages/translations). With I18n, editors can navigate the interface and read API error messages in their preferred language. This is similar to [Localization](./localization), but instead of managing translations for the data itself, you are managing translations for your application's interface.
|
||||
|
||||
By default, Payload comes with preinstalled with English, but you can easily load other languages into your own application. Languages are automatically detected based on the request. If no language was detected, or if the user's language is not yet supported by your application, English will be chosen.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: overview, config, configuration, documentation, Content Management Sys
|
||||
|
||||
Payload is a _config-based_, code-first CMS and application framework. The Payload Config is central to everything that Payload does, allowing for deep configuration of your application through a simple and intuitive API. The Payload Config is a fully-typed JavaScript object that can be infinitely extended upon.
|
||||
|
||||
Everything from your [Database](../database/overview) choice to the appearance of the [Admin Panel](../admin/overview) is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
|
||||
Everything from your [Database](../database/overview) choice, to the appearance of the [Admin Panel](../admin/overview), is fully controlled through the Payload Config. From here you can define [Fields](../fields/overview), add [Localization](./localization), enable [Authentication](../authentication/overview), configure [Access Control](../access-control/overview), and so much more.
|
||||
|
||||
The Payload Config is a `payload.config.ts` file typically located in the root of your project:
|
||||
|
||||
@@ -29,7 +29,7 @@ The Payload Config is strongly typed and ties directly into Payload's TypeScript
|
||||
|
||||
## Config Options
|
||||
|
||||
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data through [Fields](../fields/overview).
|
||||
To author your Payload Config, first determine which [Database](../database/overview) you'd like to use, then use [Collections](./collections) or [Globals](./globals) to define the schema of your data.
|
||||
|
||||
Here is one of the simplest possible Payload configs:
|
||||
|
||||
|
||||
@@ -12,13 +12,18 @@ Examples are changing every day, so be sure to check back often to see what new
|
||||
|
||||
- [Auth](https://github.com/payloadcms/payload/tree/main/examples/auth)
|
||||
- [Custom Components](https://github.com/payloadcms/payload/tree/main/examples/custom-components)
|
||||
- [Custom Server](https://github.com/payloadcms/payload/tree/main/examples/custom-server)
|
||||
- [Draft Preview](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
|
||||
- [Email](https://github.com/payloadcms/payload/tree/main/examples/email)
|
||||
- [Form Builder](https://github.com/payloadcms/payload/tree/main/examples/form-builder)
|
||||
- [Hierarchy](https://github.com/payloadcms/payload/tree/main/examples/hierarchy)
|
||||
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
|
||||
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
|
||||
- [Nested Docs](https://github.com/payloadcms/payload/tree/main/examples/nested-docs)
|
||||
- [Redirects](https://github.com/payloadcms/payload/tree/main/examples/redirects)
|
||||
- [Tailwind / Shadcn-ui](https://github.com/payloadcms/payload/tree/main/examples/tailwind-shadcn-ui)
|
||||
- [Tests](https://github.com/payloadcms/payload/tree/main/examples/testing)
|
||||
- [Virtual Fields](https://github.com/payloadcms/payload/tree/main/examples/virtual-fields)
|
||||
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
|
||||
|
||||
When necessary, some examples include a front-end. Examples that require a front-end share this folder structure:
|
||||
|
||||
@@ -84,51 +84,6 @@ The Blocks Field inherits all of the default options from the base [Field Admin
|
||||
| **`initCollapsed`** | Set the initial collapsed state |
|
||||
| **`isSortable`** | Disable order sorting by setting this value to `false` |
|
||||
|
||||
#### Customizing the way your block is rendered in Lexical
|
||||
|
||||
If you're using this block within the [Lexical editor](/docs/lexical/overview), you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.
|
||||
|
||||
- `admin.components.Label` - pass a custom React component here to customize the way that the label is rendered for this block
|
||||
- `admin.components.Block` - pass a component here to completely override the way the block is rendered in Lexical with your own component
|
||||
|
||||
This is super handy if you'd like to present your editors with a very deliberate and nicely designed block "preview" right in your rich text.
|
||||
|
||||
For example, if you have a `gallery` block, you might want to actually render the gallery of images directly in your Lexical block. With the `admin.components.Block` property, you can do exactly that!
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong><br/>
|
||||
If you customize the way your block is rendered in Lexical, you can import utility components to easily edit / remove your block - so that you don't have to build all of this yourself.
|
||||
</Banner>
|
||||
|
||||
To import these utility components for one of your custom blocks, you can import the following:
|
||||
|
||||
```ts
|
||||
import {
|
||||
// Edit block buttons (choose the one that corresponds to your usage)
|
||||
// When clicked, this will open a drawer with your block's fields
|
||||
// so your editors can edit them
|
||||
InlineBlockEditButton,
|
||||
BlockEditButton,
|
||||
|
||||
// Buttons that will remove this block from Lexical
|
||||
// (choose the one that corresponds to your usage)
|
||||
InlineBlockRemoveButton,
|
||||
BlockRemoveButton,
|
||||
|
||||
// The label that should be rendered for an inline block
|
||||
InlineBlockLabel,
|
||||
|
||||
// The default "container" that is rendered for an inline block
|
||||
// if you want to re-use it
|
||||
InlineBlockContainer,
|
||||
|
||||
// The default "collapsible" UI that is rendered for a regular block
|
||||
// if you want to re-use it
|
||||
BlockCollapsible,
|
||||
|
||||
} from '@payloadcms/richtext-lexical/client'
|
||||
```
|
||||
|
||||
## Block Configs
|
||||
|
||||
Blocks are defined as separate configs of their own.
|
||||
|
||||
@@ -86,7 +86,7 @@ _\* This property is passed directly to [react-datepicker](https://github.com/Ha
|
||||
|
||||
These properties only affect how the date is displayed in the UI. The full date is always stored in the format `YYYY-MM-DDTHH:mm:ss.SSSZ` (e.g. `1999-01-01T8:00:00.000+05:00`).
|
||||
|
||||
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v4.1.0/docs/format].
|
||||
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v2.29.3/docs/format].
|
||||
|
||||
`pickerAppearance` sets the appearance of the **react datepicker**, the options available are `dayAndTime`, `dayOnly`, `timeOnly`, and `monthOnly`. By default, the datepicker will display `dayOnly`.
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ Payload's rich text field is built on an "adapter pattern" which lets you specif
|
||||
|
||||
Right now, Payload is officially supporting two rich text editors:
|
||||
|
||||
1. [SlateJS](/docs/rich-text/slate) - legacy, backwards-compatible with 1.0
|
||||
2. [Lexical](/docs/lexical/overview) - recommended
|
||||
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
|
||||
2. [Lexical](/docs/lexical/overview) - beta, where things will be moving
|
||||
|
||||
<Banner type="success">
|
||||
<strong>
|
||||
|
||||
@@ -68,10 +68,15 @@ Here's a quick example of a React Server Component fetching data using the Local
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import config from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
|
||||
const MyServerComponent: React.FC = () => {
|
||||
const payload = await getPayload({ config })
|
||||
// If you're working in Next.js, and you want HMR,
|
||||
// you should get Payload via the `getPayloadHMR` function.
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
// If you are writing a standalone script and do not need HMR,
|
||||
// you can get Payload via import { getPayload } from 'payload' instead.
|
||||
|
||||
// The `findResult` here will be fully typed as `PaginatedDocs<Page>`,
|
||||
// where you will have the `docs` that are returned as well as
|
||||
|
||||
@@ -24,14 +24,14 @@ Payload requires the following software:
|
||||
To quickly scaffold a new Payload app in the fastest way possible, you can use [create-payload-app](https://npmjs.com/package/create-payload-app). To do so, run the following command:
|
||||
|
||||
```
|
||||
npx create-payload-app
|
||||
npx create-payload-app@beta
|
||||
```
|
||||
|
||||
Then just follow the prompts! You'll get set up with a new folder and a functioning Payload app inside. You can then start [configuring your application](../configuration/overview).
|
||||
|
||||
## Adding to an existing app
|
||||
|
||||
Adding Payload to an existing Next.js app is super straightforward. You can either run the `npx create-payload-app` command inside your Next.js project's folder, or manually install Payload by following the steps below.
|
||||
Adding Payload to an existing Next.js app is super straightforward. You can either run the `npx create-payload-app@beta` command inside your Next.js project's folder, or manually install Payload by following the steps below.
|
||||
|
||||
If you don't have a Next.js app already, but you still want to start a project from a blank Next.js app, you can create a new Next.js app using `npx create-next-app` - and then just follow the steps below to install Payload.
|
||||
|
||||
@@ -44,7 +44,7 @@ If you don't have a Next.js app already, but you still want to start a project f
|
||||
First, you'll want to add the required Payload packages to your project and can do so by running the command below:
|
||||
|
||||
```bash
|
||||
pnpm i payload @payloadcms/next @payloadcms/richtext-lexical sharp graphql
|
||||
pnpm i payload@beta @payloadcms/next@beta @payloadcms/richtext-lexical@beta sharp graphql
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
@@ -58,12 +58,12 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
|
||||
- To install the [MongoDB Adapter](../database/mongodb), run:
|
||||
```bash
|
||||
pnpm i @payloadcms/db-mongodb
|
||||
pnpm i @payloadcms/db-mongodb@beta
|
||||
```
|
||||
|
||||
- To install the [Postgres Adapter](../database/postgres), run:
|
||||
```bash
|
||||
pnpm i @payloadcms/db-postgres
|
||||
pnpm i @payloadcms/db-postgres@beta
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
@@ -73,7 +73,7 @@ To install a Database Adapter, you can run **one** of the following commands:
|
||||
|
||||
#### 2. Copy Payload files into your Next.js app folder
|
||||
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/beta/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
|
||||
@@ -3,7 +3,7 @@ title: What is Payload?
|
||||
label: What is Payload?
|
||||
order: 10
|
||||
desc: Payload is a next-gen application framework that can be used as a Content Management System, enterprise tool framework, headless commerce platform, or digital asset management tool.
|
||||
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react
|
||||
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<YouTube
|
||||
@@ -95,6 +95,12 @@ Payload can integrate with any payment processor like Stripe and its content aut
|
||||
|
||||
If you can build your storefront with a single backend, and only offload things like payment processing, the code will be simpler and the editing experience will be significantly streamlined. Manage products, catalogs, page content, media, and more—all in one spot.
|
||||
|
||||
Payload's official Ecommerce template gives you everything you need for a storefront out of the box, including a Next.js frontend, product variations, and a full Stripe implementation:
|
||||
|
||||
```
|
||||
npx create-payload-app@latest -t ecommerce
|
||||
```
|
||||
|
||||
### Digital Asset Management
|
||||
|
||||
Payload's API-first tagging, sorting, and querying engine lends itself perfectly to all types of content that a CMS might ordinarily store, but these strong fundamentals also make it a formidable Digital Asset Management (DAM) tool as well.
|
||||
|
||||
@@ -13,7 +13,7 @@ In Payload the schema is controlled by your collections and globals. All you nee
|
||||
Install `@payloadcms/graphql` as a dev dependency:
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/graphql -D
|
||||
pnpm add @payloadcms/graphql@beta -D
|
||||
```
|
||||
|
||||
Run the following command to generate the schema:
|
||||
|
||||
@@ -32,7 +32,7 @@ Examples:
|
||||
|
||||
**Periodic sync or similar scheduled action**
|
||||
|
||||
Some applications may need to perform a regularly scheduled operation of some type. Jobs are perfect for this because you can execute their logic using `cron`, scheduled nightly, every twelve hours, or some similar time period.
|
||||
Some applications may need to perform a regularly scheduled operation of some type. Jobs are perfect for this because you can execute their logic using `cron`, scheduled nightly, every twelve hours, or some similar time period.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -49,22 +49,22 @@ Examples:
|
||||
- You need to create (and then keep in sync) vector embeddings of your documents as they change, but you use an open source model to generate embeddings
|
||||
- You have a PDF generator that needs to dynamically build and send PDF versions of documents to customers
|
||||
- You need to use a headless browser to perform some type of logic
|
||||
- You need to perform a series of actions, each of which depends on a prior action and should be run in as "durable" of a fashion as possible
|
||||
- You need to perform a series of actions, each of which depends on a prior action and should be run in as "durable" of a fashion as possible
|
||||
|
||||
### How it works
|
||||
|
||||
There are a few concepts that you should become familiarized with before using Payload's Jobs Queue. We recommend learning what each of these does in order to fully understand how to leverage the power of Payload's Jobs Queue.
|
||||
|
||||
1. [Tasks](/docs/jobs-queue/tasks)
|
||||
1. [Workflows](/docs/jobs-queue/workflows)
|
||||
1. [Jobs](/docs/jobs-queue/jobs)
|
||||
1. [Queues](/docs/jobs-queue/queues)
|
||||
1. [Tasks](/docs/beta/jobs-queue/tasks)
|
||||
1. [Workflows](/docs/beta/jobs-queue/workflows)
|
||||
1. [Jobs](/docs/beta/jobs-queue/jobs)
|
||||
1. [Queues](/docs/beta/jobs-queue/queues)
|
||||
|
||||
All of these pieces work together in order to allow you to offload long-running, expensive, or future scheduled work from your main APIs.
|
||||
All of these pieces work together in order to allow you to offload long-running, expensive, or future scheduled work from your main APIs.
|
||||
|
||||
Here's a quick overview:
|
||||
|
||||
- A Task is a specific function that performs business logic
|
||||
- Workflows are groupings of specific tasks which should be run in-order, and can be retried from a specific point of failure
|
||||
- A Job is an instance of a single task or workflow which will be executed
|
||||
- A Queue is a way to segment your jobs into different "groups" - for example, some to run nightly, and others to run every 10 minutes
|
||||
- A Queue is a way to segment your jobs into different "groups" - for example, some to run nightly, and others to run every 10 minutes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Tasks
|
||||
label: Tasks
|
||||
order: 20
|
||||
desc: A Task is a distinct function declaration that can be run within Payload's Jobs Queue.
|
||||
desc: A Task is a distinct function declaration that can be run within Payload's Jobs Queue.
|
||||
keywords: jobs queue, application framework, typescript, node, react, nextjs
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs
|
||||
A <strong>"Task"</strong> is a function definition that performs business logic and whose input and output are both strongly typed.
|
||||
</Banner>
|
||||
|
||||
You can register Tasks on the Payload config, and then create [Jobs](/docs/jobs-queue/jobs) or [Workflows](/docs/jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
|
||||
You can register Tasks on the Payload config, and then create [Jobs](/docs/beta/jobs-queue/jobs) or [Workflows](/docs/beta/jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
|
||||
|
||||
Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried.
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ The Server Feature, just like the Client Feature, allows you to add markdown tra
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
import type { ElementTransformer } from '@payloadcms/richtext-lexical/lexical/markdown'
|
||||
import type { ElementTransformer } from '@lexical/markdown'
|
||||
import {
|
||||
$createMyNode,
|
||||
$isMyNode,
|
||||
@@ -299,9 +299,9 @@ import type {
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
SerializedLexicalNode,
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
} from 'lexical'
|
||||
|
||||
import { $applyNodeReplacement, DecoratorNode } from '@payloadcms/richtext-lexical/lexical'
|
||||
import { $applyNodeReplacement, DecoratorNode } from 'lexical'
|
||||
|
||||
// SerializedLexicalNode is the default lexical node.
|
||||
// By setting your SerializedMyNode type to SerializedLexicalNode,
|
||||
@@ -448,17 +448,17 @@ Example plugin.tsx:
|
||||
'use client'
|
||||
import type {
|
||||
LexicalCommand,
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
} from 'lexical'
|
||||
|
||||
import {
|
||||
createCommand,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
} from 'lexical'
|
||||
|
||||
import { useLexicalComposerContext } from '@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext.js'
|
||||
import { $insertNodeToNearestRoot } from '@payloadcms/richtext-lexical/lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||
import { $insertNodeToNearestRoot } from '@lexical/utils'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import type { PluginComponent } from '@payloadcms/richtext-lexical' // type imports can be imported from @payloadcms/richtext-lexical - even on the client
|
||||
@@ -589,7 +589,7 @@ import { createClientFeature, toolbarAddDropdownGroupWithItems } from '@payloadc
|
||||
import { IconComponent } from './icon';
|
||||
import { $isHorizontalRuleNode } from './nodes/MyNode';
|
||||
import { INSERT_MYNODE_COMMAND } from './plugin';
|
||||
import { $isNodeSelection } from '@payloadcms/richtext-lexical/lexical'
|
||||
import { $isNodeSelection } from 'lexical'
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
toolbarFixed: {
|
||||
@@ -711,7 +711,7 @@ The Client Feature, just like the Server Feature, allows you to add markdown tra
|
||||
|
||||
```ts
|
||||
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
||||
import type { ElementTransformer } from '@payloadcms/richtext-lexical/lexical/markdown'
|
||||
import type { ElementTransformer } from '@lexical/markdown'
|
||||
import {
|
||||
$createMyNode,
|
||||
$isMyNode,
|
||||
@@ -836,4 +836,4 @@ The reason the client feature does not have the same props available as the serv
|
||||
|
||||
## More information
|
||||
|
||||
Have a look at the [features we've already built](https://github.com/payloadcms/payload/tree/main/packages/richtext-lexical/src/features) - understanding how they work will help you understand how to create your own. There is no difference between the features included by default and the ones you create yourself - since those features are all isolated from the "core", you have access to the same APIs, whether the feature is part of Payload or not!
|
||||
Have a look at the [features we've already built](https://github.com/payloadcms/payload/tree/beta/packages/richtext-lexical/src/features) - understanding how they work will help you understand how to create your own. There is no difference between the features included by default and the ones you create yourself - since those features are all isolated from the "core", you have access to the same APIs, whether the feature is part of Payload or not!
|
||||
|
||||
@@ -200,7 +200,7 @@ Lexical provides a seamless way to perform conversions between various other for
|
||||
A headless editor can perform such conversions outside of the main editor instance. Follow this method to initiate a headless editor:
|
||||
|
||||
```ts
|
||||
import { createHeadlessEditor } from '@payloadcms/richtext-lexical/lexical/headless'
|
||||
import { createHeadlessEditor } from '@lexical/headless' // <= make sure this package is installed
|
||||
import { getEnabledNodes, sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
|
||||
const yourEditorConfig // <= your editor config here
|
||||
@@ -237,7 +237,7 @@ If you have access to the sanitized collection config, you can get access to the
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig, RichTextField } from 'payload'
|
||||
import { createHeadlessEditor } from '@payloadcms/richtext-lexical/lexical/headless'
|
||||
import { createHeadlessEditor } from '@lexical/headless'
|
||||
import type { LexicalRichTextAdapter, SanitizedServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
import {
|
||||
getEnabledNodes,
|
||||
@@ -292,8 +292,8 @@ export const MyCollection: CollectionConfig = {
|
||||
Once you have your headless editor instance, you can use it to convert HTML to Lexical:
|
||||
|
||||
```ts
|
||||
import { $generateNodesFromDOM } from '@payloadcms/richtext-lexical/lexical/html'
|
||||
import { $getRoot, $getSelection } from '@payloadcms/richtext-lexical/lexical'
|
||||
import { $generateNodesFromDOM } from '@lexical/html'
|
||||
import { $getRoot, $getSelection } from 'lexical'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
headlessEditor.update(
|
||||
@@ -334,7 +334,8 @@ This has been taken from the [lexical serialization & deserialization docs](http
|
||||
Convert markdown content to the Lexical editor format with the following:
|
||||
|
||||
```ts
|
||||
import { sanitizeServerEditorConfig, $convertFromMarkdownString } from '@payloadcms/richtext-lexical'
|
||||
import { $convertFromMarkdownString } from '@lexical/markdown'
|
||||
import { sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig, payloadConfig) // <= your editor config & Payload Config here
|
||||
const markdown = `# Hello World`
|
||||
@@ -360,9 +361,9 @@ Export content from the Lexical editor into Markdown format using these steps:
|
||||
Here's the code for it:
|
||||
|
||||
```ts
|
||||
import { $convertToMarkdownString } from '@payloadcms/richtext-lexical/lexical/markdown'
|
||||
import { $convertToMarkdownString } from '@lexical/markdown'
|
||||
import { sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { SerializedEditorState } from 'lexical'
|
||||
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig, payloadConfig) // <= your editor config & Payload Config here
|
||||
const yourEditorState: SerializedEditorState // <= your current editor state here
|
||||
@@ -396,8 +397,8 @@ Export content from the Lexical editor into plain text using these steps:
|
||||
Here's the code for it:
|
||||
|
||||
```ts
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
import { $getRoot } from '@payloadcms/richtext-lexical/lexical'
|
||||
import type { SerializedEditorState } from 'lexical'
|
||||
import { $getRoot } from 'lexical'
|
||||
|
||||
const yourEditorState: SerializedEditorState // <= your current editor state here
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ One of Payload's goals is to build the best rich text editor experience that we
|
||||
|
||||
Classically, we've used SlateJS to work toward this goal, but building custom elements into Slate has proven to be more difficult than we'd like, and we've been keeping our options open.
|
||||
|
||||
<Banner type="warning">
|
||||
Payload's Lexical rich text editor is currently in beta. It's stable enough to use as you build on
|
||||
Payload, so if you're up for helping us fine-tune it, you should use it. But if you're looking for
|
||||
stability, use Slate instead.
|
||||
</Banner>
|
||||
|
||||
Lexical is extremely impressive and trivializes a lot of the hard parts of building new elements into a rich text editor. It has a few distinct advantages over Slate, including the following:
|
||||
|
||||
1. A "/" menu, which allows editors to easily add new elements while never leaving their keyboard
|
||||
|
||||
@@ -34,11 +34,11 @@ Then, render the `RefreshRouteOnSave` component anywhere in your `page.tsx`. Her
|
||||
|
||||
```tsx
|
||||
import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'
|
||||
import { getPayload } from 'payload'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '../payload.config'
|
||||
|
||||
export default async function Page() {
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
const page = await payload.findByID({
|
||||
collection: 'pages',
|
||||
|
||||
@@ -18,9 +18,12 @@ Payload can be used completely outside of Next.js which is helpful in cases like
|
||||
|
||||
Payload provides a convenient way to run standalone scripts, which can be useful for tasks like seeding your database or performing one-off operations.
|
||||
|
||||
In standalone scripts, you can simply import the Payload Config and use it right away. If you need an initialized copy of Payload, you can then use the `getPayload` function. This can be useful for tasks like seeding your database or performing other one-off operations.
|
||||
In standalone scripts, can simply import the Payload Config and use it right away. If you need an initialized copy of Payload, you can then use the `getPayload` function. This can be useful for tasks like seeding your database or performing other one-off operations.
|
||||
|
||||
```ts
|
||||
// We are importing `getPayload` because we don't need HMR
|
||||
// for a standalone script. For usage of Payload inside Next.js,
|
||||
// you should always use `import { getPayloadHMR } from '@payloadcms/next/utilities'` instead.
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
|
||||
|
||||
@@ -43,6 +43,27 @@ const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } })
|
||||
|
||||
If you want to import Payload in places where you don't have the option to access it from function arguments or `req`, you can import it and initialize it.
|
||||
|
||||
There are two places to import Payload:
|
||||
|
||||
**Option 1 - using HMR, within Next.js**
|
||||
|
||||
```ts
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '@payload-config'
|
||||
|
||||
const payload = await getPayloadHMR({ config })
|
||||
```
|
||||
|
||||
You should import Payload using the first option (`getPayloadHMR`) if you are using Payload inside of Next.js (like route handlers, server components, and similar.)
|
||||
|
||||
This way, in Next.js development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload Config, your usage of Payload will always be in sync with your changes. In production, `getPayloadHMR` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
|
||||
|
||||
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
|
||||
|
||||
**Option 2 - outside of Next.js**
|
||||
|
||||
If you are using Payload outside of Next.js, for example in standalone scripts or in other frameworks, you can import Payload with no HMR functionality. Instead of using `getPayloadHMR`, you can use `getPayload`.
|
||||
|
||||
```ts
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
@@ -50,11 +71,7 @@ import config from '@payload-config'
|
||||
const payload = await getPayload({ config })
|
||||
```
|
||||
|
||||
If you're working in Next.js' development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload Config, your usage of Payload will always be in sync with your changes. In production, `getPayload` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
|
||||
|
||||
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
|
||||
|
||||
For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
|
||||
Both options function in exactly the same way outside of one having HMR support and the other not. For more information about using Payload outside of Next.js, [click here](./outside-nextjs).
|
||||
|
||||
## Local options available
|
||||
|
||||
|
||||
@@ -1,121 +1,15 @@
|
||||
---
|
||||
title: 2.0 to 3.0 Migration Guide
|
||||
label: 2.0 to 3.0 Migration Guide
|
||||
order: 10
|
||||
desc: Upgrade guide for Payload 2.x projects migrating to 3.0.
|
||||
keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react
|
||||
---
|
||||
# 🚧 **DRAFT:** 3.0 Migration Guide / Breaking Changes
|
||||
|
||||
# Payload 2.0 to 3.0 Migration Guide
|
||||
|
||||
Payload 3.0 completely replatforms the Admin Panel from a React Router single-page application onto the Next.js App Router with full support for React Server Components. This change completely separates Payload "core" from its rendering and HTTP layers, making it truly Node-safe and portable.
|
||||
> [!IMPORTANT]
|
||||
> This document will continue to be updated and cleaned up until the 3.0 release.
|
||||
|
||||
## What has changed?
|
||||
|
||||
The core logic and principles of Payload remain the same from 2.0 to 3.0, with the majority of changes affecting specifically the HTTP layer and the Admin Panel, which is now built upon Next.js. With this change, your entire application can be served within a single repo, with Payload endpoints are now opened within your own Next.js application, directly alongside your frontend. Payload is still headless, you will still be able to leverage it completely headlessly just as you do now with Sveltekit, etc. All Payload APIs remain exactly the same (with a few new features), and the Payload Config is generally the same, with the breaking changes detailed below.
|
||||
The core logic and principles of Payload remain the same from 2.0 to 3.0, with the majority of changes affecting specifically the HTTP layer and the Admin Panel, which is now built upon Next.js. With this change, your entire application can be served within a single repo. Payload endpoints are now opened directly within your own Next.js application, right alongside your frontend. All Payload APIs remain exactly the same (with a few new features), and the Payload Config is generally the same, with the breaking changes detailed below.
|
||||
|
||||
### Table of Contents
|
||||
Payload is still headless, you will still be able to leverage it completely headlessly just as you do now with Sveltekit, etc. In fact, Payload itself is now _truly_ portable because it is fully Node.js compatible and completely separate from the HTTP layer. The entire Payload suite of packages has been modularized to make this possible.
|
||||
|
||||
All breaking changes are listed below. If you encounter changes that are not explicitly listed here, please consider contributing to this documentation by submitting a PR.
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [Custom Components](#custom-components)
|
||||
- [Endpoints](#endpoints)
|
||||
- [React Hooks](#react-hooks)
|
||||
- [Types](#types)
|
||||
- [Email Adapters](#email-adapters)
|
||||
- [Plugins](#plugins)
|
||||
|
||||
## Installation
|
||||
|
||||
Payload 3.0 requires a set of auto-generated files that you will need to bring into your existing project. The easiest way of acquiring these is by initializing a new project via `create-payload-app`, then replace the provided Payload Config with your own.
|
||||
|
||||
```bash
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
1. **Install new dependencies of payload, next.js and react**:
|
||||
|
||||
Refer to the package.json file made in the create-payload-app, including peerDependencies, devDependencies, and dependencies. The core package and plugins require all versions to be synced. Previously, on 2.x it was possible to be running the latest version of payload 2.x with an older version of db-mongodb for example. This is no longer the case.
|
||||
|
||||
```bash
|
||||
pnpm i next react react-dom payload @payloadcms/ui @payloadcms/next
|
||||
```
|
||||
|
||||
Also install the other @payloadcms packages specific to the plugins and adapters you are using. Depending on your project, these may include:
|
||||
- @payloadcms/db-mongodb
|
||||
- @payloadcms/db-postgres
|
||||
- @payloadcms/richtext-slate
|
||||
- @payloadcms/richtext-lexical
|
||||
- @payloadcms/plugin-form-builder
|
||||
- @payloadcms/plugin-nested-docs
|
||||
- @payloadcms/plugin-redirects
|
||||
- @payloadcms/plugin-relationship
|
||||
- @payloadcms/plugin-search
|
||||
- @payloadcms/plugin-sentry
|
||||
- @payloadcms/plugin-seo
|
||||
- @payloadcms/plugin-stripe
|
||||
- @payloadcms/plugin-cloud-storage - Read [More](#@payloadcms/plugin-cloud-storage).
|
||||
|
||||
1. Uninstall deprecated packages:
|
||||
|
||||
```bash
|
||||
pnpm remove express nodemon @payloadcms/bundler-webpack @payloadcms/bundler-vite
|
||||
```
|
||||
|
||||
1. For Payload Cloud users, the plugin has changed.
|
||||
|
||||
Uninstall the old package:
|
||||
|
||||
```bash
|
||||
pnpm remove @payloadcms/plugin-cloud
|
||||
```
|
||||
|
||||
Install the new package:
|
||||
|
||||
```bash
|
||||
pnpm i @payloadcms/payload-cloud
|
||||
```
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
- import { payloadCloud } from '@payloadcms/plugin-cloud'
|
||||
+ import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
|
||||
|
||||
buildConfig({
|
||||
// ...
|
||||
plugins: [
|
||||
- payloadCloud()
|
||||
+ payloadCloudPlugin()
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
1. **Optional** sharp dependency
|
||||
|
||||
If you have upload enabled collections that use `formatOptions`, `imageSizes`, or `resizeOptions`—payload expects to have `sharp` installed. In 2.0 this was a dependency was installed for you. Now it is only installed if needed. If you have any of these options set, you will need to install `sharp` and add it to your payload.config.ts:
|
||||
|
||||
```bash
|
||||
pnpm i sharp
|
||||
```
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import sharp from 'sharp'
|
||||
buildConfig({
|
||||
// ...
|
||||
+ sharp,
|
||||
})
|
||||
```
|
||||
|
||||
1. Database Adapter Migrations
|
||||
|
||||
If you have existing data and are using the MongoDB or Postgres adapters, you will need to run the database migrations to ensure your database schema is up-to-date. Follow the instructions from the release notes for [postgres](https://github.com/payloadcms/payload/releases/edit/v3.0.0-beta.39) or [mongodb](https://github.com/payloadcms/payload/releases/edit/v3.0.0-beta.131) depending on your chosen adapter.
|
||||
|
||||
## Breaking Changes
|
||||
## To migrate from Payload 2.0 to 3.0:
|
||||
|
||||
1. Delete the `admin.bundler` property from your Payload Config. Payload no longer bundles the Admin Panel. Instead, we rely directly on Next.js for bundling.
|
||||
|
||||
@@ -145,23 +39,6 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
})
|
||||
```
|
||||
|
||||
1. Environment variables prefixed with `PAYLOAD_PUBLIC` will no longer be available on the client. In order to access them on the client, those will now have to be prefixed with `NEXT_PUBLIC` instead.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- const var = process.env.PAYLOAD_PUBLIC_MY_ENV_VAR
|
||||
+ const var = process.env.NEXT_PUBLIC_MY_ENV_VAR
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/configuration/environment-vars).
|
||||
|
||||
1. The `req` object used to extend the [Express Request](https://expressjs.com/), but now extends the [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). You may need to update your code accordingly to reflect this change. For example:
|
||||
|
||||
```diff
|
||||
- req.headers['content-type']
|
||||
+ req.headers.get('content-type')
|
||||
```
|
||||
|
||||
1. The `admin.css` and `admin.scss` properties in the Payload Config have been removed.
|
||||
|
||||
```diff
|
||||
@@ -256,159 +133,6 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
}
|
||||
```
|
||||
|
||||
1. Fields with `unique: true` now automatically be appended with “- Copy” through the new `admin.beforeDuplicate` field hooks (see previous bullet).
|
||||
|
||||
1. The `upload.staticDir` property must now be an absolute path. Before it would attempt to use the location of the Payload Config and merge the relative path set for staticDir.
|
||||
|
||||
```diff
|
||||
// collections/Media.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import path from 'path'
|
||||
+ import { fileURLToPath } from 'url'
|
||||
|
||||
+ const filename = fileURLToPath(import.meta.url)
|
||||
+ const dirname = path.dirname(filename)
|
||||
|
||||
export const MediaCollection: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
- staticDir: path.resolve(__dirname, './uploads'),
|
||||
+ staticDir: path.resolve(dirname, '../uploads'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
1. The `upload.staticURL` property has been removed. If you were using this format URLs when using an external provider, you can leverage the `generateFileURL` functions in order to do the same.
|
||||
|
||||
```diff
|
||||
// collections/Media.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const MediaCollection: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
- staticURL: '',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
1. The `admin.favicon` property is now `admin.icons` and the types have changed:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
- favicon: 'path-to-favicon.svg',
|
||||
+ icons: [{
|
||||
+ path: 'path-to-favicon.svg',
|
||||
+ sizes: '32x32'
|
||||
+ }]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/admin/metadata#icons).
|
||||
|
||||
1. The `admin.meta.ogImage` property has been replaced by `admin.meta.openGraph.images`:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
meta: {
|
||||
- ogImage: '',
|
||||
+ openGraph: {
|
||||
+ images: []
|
||||
+ }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/admin/metadata#open-graph).
|
||||
|
||||
1. The args of the `admin.livePreview.url` function have changed. It no longer receives `documentInfo` as an arg, and instead, now has `collectionConfig` and `globalConfig`.
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
// ...
|
||||
livePreview: ({
|
||||
- documentInfo,
|
||||
+ collectionConfig,
|
||||
+ globalConfig
|
||||
}) => ''
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
1. The `admin.logoutRoute` and `admin.inactivityRoute` properties have been consolidated into a single `admin.routes` property. To migrate, simply move those two keys as follows:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
- logoutRoute: '/custom-logout',
|
||||
+ inactivityRoute: '/custom-inactivity'
|
||||
+ routes: {
|
||||
+ logout: '/custom-logout',
|
||||
+ inactivity: '/custom-inactivity'
|
||||
+ }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
1. The `custom` property in the Payload Config, i.e. Collections, Globals, and Fields is now **server only** and will **not** appear in the client-side bundle. To add custom properties to the client bundle, use the new `admin.custom` property, which will be available on _both_ the server and the client.
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
custom: {
|
||||
someProperty: 'My Server Prop' // Now server only!
|
||||
},
|
||||
admin: {
|
||||
+ custom: {
|
||||
+ name: 'My Client Prop' // Available in server AND client
|
||||
+ }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
1. `hooks.afterError` is now an array of functions instead of a single function. The args have also been expanded. Read [More](https://payloadcms.com/docs/hooks/overview#root-hooks).
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
hooks: {
|
||||
- afterError: async ({ error }) => {
|
||||
+ afterError: [
|
||||
+ async ({ error, req, res }) => {
|
||||
+ // ...
|
||||
+ }
|
||||
+ ]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Components
|
||||
|
||||
1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths:
|
||||
|
||||
```diff
|
||||
@@ -417,6 +141,13 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
```
|
||||
*Note: for brevity, not _all_ modules are listed here*
|
||||
|
||||
1. The `BlockField` and related types have been renamed to `BlocksField` for semantic accuracy.
|
||||
|
||||
```diff
|
||||
- import type { BlockField, BlockFieldProps } from 'payload'
|
||||
+ import type { BlocksField, BlocksFieldProps } from 'payload'
|
||||
```
|
||||
|
||||
1. All Custom Components are now defined as _file paths_ instead of direct imports. If you are using Custom Components in your Payload Config, remove the imported module and point to the file's path instead:
|
||||
|
||||
```diff
|
||||
@@ -436,7 +167,7 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/admin/components#component-paths).
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#component-paths).
|
||||
|
||||
1. All Custom Components are now server-rendered by default, and therefore, cannot use state or hooks directly. If you’re using Custom Components in your app that requires state or hooks, add the `'use client'` directive at the top of the file.
|
||||
|
||||
@@ -456,186 +187,102 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/admin/components#client-components).
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#client-components).
|
||||
|
||||
1. The `admin.description` property within Collection, Globals, and Fields no longer accepts a React Component. Instead, you must define it as a Custom Component.
|
||||
|
||||
1. For Collections, use the `admin.components.edit.Description` key:
|
||||
1. The `custom` property in the Payload Config, i.e. Collections, Globals, and Fields is now **server only** and will **not** appear in the client-side bundle. To add custom properties to the client bundle, use the new `admin.custom` property, which will be available on _both_ the server and the client.
|
||||
|
||||
```diff
|
||||
// collections/Posts.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
- import { MyCustomDescription } from '../components/MyCustomDescription'
|
||||
|
||||
export const PostsCollection: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
admin: {
|
||||
- description: MyCustomDescription,
|
||||
+ components: {
|
||||
+ edit: {
|
||||
+ Description: 'path/to/MyCustomDescription'
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. For Globals, use the `admin.components.elements.Description` key:
|
||||
|
||||
```diff
|
||||
// globals/Site.ts
|
||||
import type { GlobalConfig } from 'payload'
|
||||
- import { MyCustomDescription } from '../components/MyCustomDescription'
|
||||
|
||||
export const SiteGlobal: GlobalConfig = {
|
||||
slug: 'site',
|
||||
admin: {
|
||||
- description: MyCustomDescription,
|
||||
+ components: {
|
||||
+ elements: {
|
||||
+ Description: 'path/to/MyCustomDescription'
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. For Fields, use the `admin.components.Description` key:
|
||||
|
||||
```diff
|
||||
// fields/MyField.ts
|
||||
import type { FieldConfig } from 'payload'
|
||||
- import { MyCustomDescription } from '../components/MyCustomDescription'
|
||||
|
||||
export const MyField: FieldConfig = {
|
||||
type: 'text',
|
||||
admin: {
|
||||
- description: MyCustomDescription,
|
||||
+ components: {
|
||||
+ Description: 'path/to/MyCustomDescription'
|
||||
+ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Array Field row labels and Collapsible Field label now _only_ accepts a React Component, and no longer accepts a plain string or record:
|
||||
|
||||
```diff
|
||||
// file: Collection.tsx
|
||||
import type { CollectionConfig } from 'payload'
|
||||
- import { MyCustomRowLabel } from './components/MyCustomRowLabel.tsx'
|
||||
|
||||
export const MyCollection: CollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'my-array',
|
||||
type: 'array',
|
||||
admin: {
|
||||
components: {
|
||||
- RowLabel: 'My Array Row Label,
|
||||
+ RowLabel: './components/RowLabel.ts'
|
||||
}
|
||||
},
|
||||
fields: [...]
|
||||
},
|
||||
{
|
||||
name: 'my-collapsible',
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
components: {
|
||||
- Label: 'My Collapsible Label',
|
||||
+ Label: './components/RowLabel.ts'
|
||||
}
|
||||
},
|
||||
fields: [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
1. All default view keys are now camelcase:
|
||||
|
||||
For example, for Root Views:
|
||||
|
||||
```diff
|
||||
// file: payload.config.ts
|
||||
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
views: {
|
||||
- Account: ...
|
||||
+ account: ...
|
||||
}
|
||||
custom: {
|
||||
someProperty: 'My Server Prop' // Now server only!
|
||||
},
|
||||
admin: {
|
||||
+ custom: {
|
||||
+ name: 'My Client Prop' // Available in server AND client
|
||||
+ }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Or Document Views:
|
||||
1. The `useTitle` hook has been consolidated into the `useDocumentInfo` hook. Instead, you can get title directly from document info context:
|
||||
|
||||
```diff
|
||||
// file: Collection.tsx
|
||||
'use client'
|
||||
- import { useTitle } from 'payload'
|
||||
+ import { useDocumentInfo } from '@payloadcms/ui'
|
||||
|
||||
import type { CollectionConfig } from 'payload'
|
||||
export const MyComponent = () => {
|
||||
- const title = useTitle()
|
||||
+ const { title } = useDocumentInfo()
|
||||
|
||||
export const MyCollection: CollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
views: {
|
||||
- Edit: {
|
||||
- Default: ...
|
||||
- }
|
||||
+ edit: {
|
||||
+ default: ...
|
||||
+ }
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
1. Custom Views within the config no longer accept React Components directly, instead, you must use their `Component` property:
|
||||
1. The `Fields` type was renamed to `FormState` for improved semantics. If you were previously importing this type in your own application, simply change the import name:
|
||||
|
||||
```diff
|
||||
// file: Collection.tsx
|
||||
import type { CollectionConfig } from 'payload'
|
||||
- import { MyCustomView } from './components/MyCustomView.tsx'
|
||||
- import type { Fields } from 'payload'
|
||||
+ import type { FormState } from 'payload'
|
||||
```
|
||||
|
||||
export const MyCollection: CollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
views: {
|
||||
- Edit: MyCustomView
|
||||
+ edit: {
|
||||
+ Component: './components/MyCustomView.tsx'
|
||||
+ }
|
||||
}
|
||||
}
|
||||
1. The `useDocumentInfo` hook no longer returns `collection` or `global`. Instead, various properties of the config are passed, like `collectionSlug` and `globalSlug`. You can use these to access a client-side config, if needed, through the `useConfig` hook (see next bullet).
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
import { useDocumentInfo } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
const {
|
||||
- collection,
|
||||
- global,
|
||||
+ collectionSlug,
|
||||
+ globalSlug
|
||||
} = useDocumentInfo()
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This also means that Custom Root Views are no longer defined on the `edit` key. Instead, use the new `views.root` key:
|
||||
1. The `useConfig` hook now returns a `ClientConfig` and not a `SanitizedConfig`. This is because the config itself is not serializable and so it is not able to be thread through to the client. This means that all non-serializable props have been omitted from the Client Config, such as `db`, `bundler`, etc.
|
||||
|
||||
```diff
|
||||
// file: Collection.tsx
|
||||
import type { CollectionConfig } from 'payload'
|
||||
- import { MyCustomRootView } from './components/MyCustomRootView.tsx'
|
||||
'use client'
|
||||
- import { useConfig } from 'payload'
|
||||
+ import { useConfig } from '@payloadcms/ui'
|
||||
|
||||
export const MyCollection: CollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
views: {
|
||||
- Edit: MyCustomRootView
|
||||
edit: {
|
||||
+ root: {
|
||||
+ Component: './components/MyCustomRootView.tsx'
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
export const MyComponent = () => {
|
||||
- const config = useConfig() // used to be a 'SanitizedConfig'
|
||||
+ const { config } = useConfig() // now is a 'ClientConfig'
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#accessing-the-payload-config).
|
||||
|
||||
1. The args of the `admin.livePreview.url` function have changed. It no longer receives `documentInfo` as an arg, and instead, now has `collectionConfig` and `globalConfig`.
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
// ...
|
||||
livePreview: ({
|
||||
- documentInfo,
|
||||
+ collectionConfig,
|
||||
+ globalConfig
|
||||
}) => ''
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
1. The `href` and `isActive` functions on View Tabs no longer includes the `match` or `location` arguments. This is is a property specific to React Router, not Next.js. If you need to do URL matching similar to this, use a custom tab that fires of some hooks, i.e. `usePathname()` and run it through your own utility functions:
|
||||
|
||||
```diff
|
||||
@@ -677,12 +324,12 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
admin: {
|
||||
components: {
|
||||
- views: {
|
||||
- Edit: {
|
||||
- edit: {
|
||||
- Tab: {
|
||||
- pillLabel: 'Hello, world!',
|
||||
- },
|
||||
- },
|
||||
+ edit: {
|
||||
+ Edit: {
|
||||
+ tab: {
|
||||
+ Pill: './path/to/CustomPill.tsx',
|
||||
+ }
|
||||
@@ -693,6 +340,106 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
}
|
||||
```
|
||||
|
||||
1. Unique fields will now automatically be appended with “- Copy” through the new `admin.beforeDuplicate` field hooks (detailed aboved).
|
||||
|
||||
1. The `useCollapsible` hook has had slight changes to its property names. `collapsed` is now `isCollapsed` and `withinCollapsible` is now `isWithinCollapsible`.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
import { useCollapsible } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const { collapsed, withinCollapsible } = useCollapsible()
|
||||
+ const { isCollapsed, isWithinCollapsible } = useCollapsible()
|
||||
}
|
||||
```
|
||||
|
||||
1. The `admin.favicon` property is now `admin.icons` and the types have changed:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
- favicon: 'path-to-favicon.svg',
|
||||
+ icons: [{
|
||||
+ path: 'path-to-favicon.svg',
|
||||
+ sizes: '32x32'
|
||||
+ }]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/metadata#icons).
|
||||
|
||||
1. The `admin.meta.ogImage` property has been replaced by `admin.meta.openGraph.images`:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
meta: {
|
||||
- ogImage: '',
|
||||
+ openGraph: {
|
||||
+ images: []
|
||||
+ }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/metadata#open-graph).
|
||||
|
||||
1. The `admin.logoutRoute` and `admin.inactivityRoute` properties have been consolidated into a single `admin.routes` property. To migrate, simply move those two keys as follows:
|
||||
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
- logoutRoute: '/custom-logout',
|
||||
+ inactivityRoute: '/custom-inactivity'
|
||||
+ routes: {
|
||||
+ logout: '/custom-logout',
|
||||
+ inactivity: '/custom-inactivity'
|
||||
+ }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
1. Environment variables prefixed with `PAYLOAD_PUBLIC` will no longer be available on the client. In order to access them on the client, those will now have to be prefixed with `NEXT_PUBLIC` instead.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- const var = process.env.PAYLOAD_PUBLIC_MY_ENV_VAR
|
||||
+ const var = process.env.NEXT_PUBLIC_MY_ENV_VAR
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/beta/configuration/environment-vars).
|
||||
|
||||
1. The `useTranslation` hook no longer takes any options, any translations using shorthand accessors will need to use the entire `group:key`
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- import { useTranslation } from 'payload'
|
||||
+ import { useTranslation } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const { i18n, t } = useTranslation('general')
|
||||
+ const { i18n, t } = useTranslation()
|
||||
|
||||
- return <p>{t('cancel')}</p>
|
||||
+ return <p>{t('general:cancel')}</p>
|
||||
}
|
||||
```
|
||||
|
||||
1. `react-i18n` was removed, the `Trans` component from `react-i18n` has been replaced with a Payload-provided solution:
|
||||
|
||||
```diff
|
||||
@@ -720,9 +467,21 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
1. `root` endpoints no longer exist on the config. Instead, you must add them to the `/api` folder within your own Payload application.
|
||||
|
||||
1. All endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.:
|
||||
```diff
|
||||
// payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
- endpoints: [...]
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see the [Next.js Documentation](https://nextjs.org/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
1. All other endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.:
|
||||
|
||||
```diff
|
||||
// collections/Posts.ts
|
||||
@@ -797,102 +556,47 @@ For more details, see the [Documentation](https://payloadcms.com/docs/getting-st
|
||||
}
|
||||
```
|
||||
|
||||
## React Hooks
|
||||
|
||||
1. The `useTitle` hook has been consolidated into the `useDocumentInfo` hook. Instead, you can get title directly from document info context:
|
||||
1. The `req` object used to extend the [Express Request](https://expressjs.com/), but now extends the [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). You may need to update your code accordingly to reflect this change. For example:
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- import { useTitle } from 'payload'
|
||||
+ import { useDocumentInfo } from '@payloadcms/ui'
|
||||
- req.headers['content-type']
|
||||
+ req.headers.get('content-type')
|
||||
```
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const title = useTitle()
|
||||
+ const { title } = useDocumentInfo()
|
||||
1. `staticDir` must now be an absolute path. Before it would attempt to use the location of the Payload Config and merge the relative path set for staticDir.
|
||||
|
||||
// ...
|
||||
```diff
|
||||
// collections/Media.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import path from 'path'
|
||||
+ import { fileURLToPath } from 'url'
|
||||
|
||||
+ const filename = fileURLToPath(import.meta.url)
|
||||
+ const dirname = path.dirname(filename)
|
||||
|
||||
export const MediaCollection: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
- staticDir: path.resolve(__dirname, './uploads'),
|
||||
+ staticDir: path.resolve(dirname, '../uploads'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
1. The `useDocumentInfo` hook no longer returns `collection` or `global`. Instead, various properties of the config are passed, like `collectionSlug` and `globalSlug`. You can use these to access a client-side config, if needed, through the `useConfig` hook (see next bullet).
|
||||
1. `staticURL` has been removed. If you were using this format URLs when using an external provider, you can leverage the `generateFileURL` functions in order to do the same.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
import { useDocumentInfo } from '@payloadcms/ui'
|
||||
// collections/Media.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const MyComponent = () => {
|
||||
const {
|
||||
- collection,
|
||||
- global,
|
||||
+ collectionSlug,
|
||||
+ globalSlug
|
||||
} = useDocumentInfo()
|
||||
|
||||
// ...
|
||||
export const MediaCollection: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
- staticURL: '',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
1. The `useConfig` hook now returns a `ClientConfig` and not a `SanitizedConfig`. This is because the config itself is not serializable and so it is not able to be thread through to the client. This means that all non-serializable props have been omitted from the Client Config, such as `db`, `bundler`, etc.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- import { useConfig } from 'payload'
|
||||
+ import { useConfig } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const config = useConfig() // used to be a 'SanitizedConfig'
|
||||
+ const { config } = useConfig() // now is a 'ClientConfig'
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Documentation](https://payloadcms.com/docs/admin/components#accessing-the-payload-config).
|
||||
|
||||
1. The `useCollapsible` hook has had slight changes to its property names. `collapsed` is now `isCollapsed` and `withinCollapsible` is now `isWithinCollapsible`.
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
import { useCollapsible } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const { collapsed, withinCollapsible } = useCollapsible()
|
||||
+ const { isCollapsed, isWithinCollapsible } = useCollapsible()
|
||||
}
|
||||
```
|
||||
|
||||
1. The `useTranslation` hook no longer takes any options, any translations using shorthand accessors will need to use the entire `group:key`
|
||||
|
||||
```diff
|
||||
'use client'
|
||||
- import { useTranslation } from 'payload'
|
||||
+ import { useTranslation } from '@payloadcms/ui'
|
||||
|
||||
export const MyComponent = () => {
|
||||
- const { i18n, t } = useTranslation('general')
|
||||
+ const { i18n, t } = useTranslation()
|
||||
|
||||
- return <p>{t('cancel')}</p>
|
||||
+ return <p>{t('general:cancel')}</p>
|
||||
}
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
1. The `Fields` type was renamed to `FormState` for improved semantics. If you were previously importing this type in your own application, simply change the import name:
|
||||
|
||||
```diff
|
||||
- import type { Fields } from 'payload'
|
||||
+ import type { FormState } from 'payload'
|
||||
```
|
||||
|
||||
1. The `BlockField` and related types have been renamed to `BlocksField` for semantic accuracy.
|
||||
|
||||
```diff
|
||||
- import type { BlockField, BlockFieldProps } from 'payload'
|
||||
+ import type { BlocksField, BlocksFieldProps } from 'payload'
|
||||
```
|
||||
|
||||
## Email Adapters
|
||||
|
||||
Email functionality has been abstracted out into email adapters.
|
||||
@@ -957,7 +661,7 @@ export default buildConfig({
|
||||
|
||||
- Now only available if using custom server and using express or similar
|
||||
|
||||
## Plugins
|
||||
# Plugins
|
||||
|
||||
1. *All* plugins have been standardized to use _named exports_ (as opposed to default exports). Most also have a suffix of `Plugin` to make it clear what is being imported.
|
||||
|
||||
@@ -979,11 +683,11 @@ export default buildConfig({
|
||||
- If you have created a custom adapter, the type must now provide a `name` property.
|
||||
|
||||
| Service | Package |
|
||||
| -------------------- |------------------------------------------------------------------------------|
|
||||
| Vercel Blob | https://github.com/payloadcms/payload/tree/main/packages/storage-vercel-blob |
|
||||
| AWS S3 | https://github.com/payloadcms/payload/tree/main/packages/storage-s3 |
|
||||
| Azure | https://github.com/payloadcms/payload/tree/main/packages/storage-azure |
|
||||
| Google Cloud Storage | https://github.com/payloadcms/payload/tree/main/packages/storage-gcs |
|
||||
| -------------------- | ---------------------------------------------------------------------------- |
|
||||
| Vercel Blob | https://github.com/payloadcms/payload/tree/beta/packages/storage-vercel-blob |
|
||||
| AWS S3 | https://github.com/payloadcms/payload/tree/beta/packages/storage-s3 |
|
||||
| Azure | https://github.com/payloadcms/payload/tree/beta/packages/storage-azure |
|
||||
| Google Cloud Storage | https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs |
|
||||
|
||||
```tsx
|
||||
// ❌ Before (required peer dependencies depending on adapter)
|
||||
@@ -994,7 +698,7 @@ import { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3'
|
||||
plugins: [
|
||||
cloudStorage({
|
||||
collections: {
|
||||
media: {
|
||||
[mediaSlug]: {
|
||||
adapter: s3Adapter({
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
@@ -1017,7 +721,7 @@ plugins: [
|
||||
plugins: [
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: true,
|
||||
[mediaSlug]: true,
|
||||
},
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
@@ -1095,4 +799,4 @@ plugins: [
|
||||
|
||||
## `@payloadcms/richtext-lexical`
|
||||
|
||||
If you have custom features for `@payloadcms/richtext-lexical` you will need to migrate your code to the new API. Read more about the new API in the [documentation](https://payloadcms.com/docs/lexical/building-custom-features).
|
||||
// TODO: Add migration guide for `@payloadcms/richtext-lexical`
|
||||
|
||||
@@ -36,7 +36,7 @@ Forms can be as simple or complex as you need, from a basic contact form, to a m
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-form-builder
|
||||
pnpm add @payloadcms/plugin-form-builder@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -48,7 +48,7 @@ Install the plugin using any JavaScript package manager like [Yarn](https://yarn
|
||||
or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-nested-docs
|
||||
pnpm add @payloadcms/plugin-nested-docs@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
@@ -238,4 +238,10 @@ import { PluginConfig, GenerateURL, GenerateLabel } from '@payloadcms/plugin-nes
|
||||
|
||||
## Examples
|
||||
|
||||
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce), both of which use this plugin.
|
||||
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an
|
||||
official [Nested Docs Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/nested-docs) which
|
||||
demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
|
||||
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an
|
||||
official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website)
|
||||
and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce), both of which use this
|
||||
plugin.
|
||||
|
||||
@@ -32,7 +32,7 @@ For example, if you have a page at `/about` and you want to change it to `/about
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-redirects
|
||||
pnpm add @payloadcms/plugin-redirects@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
@@ -102,4 +102,4 @@ import { PluginConfig } from '@payloadcms/plugin-redirects/types'
|
||||
|
||||
## Examples
|
||||
|
||||
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce), both of which use this plugin.
|
||||
The [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples) contains an official [Redirects Plugin Example](https://github.com/payloadcms/payload/tree/main/examples/redirects) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) also contains an official [Website Template](https://github.com/payloadcms/payload/tree/main/templates/website) and [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommere), both of which use this plugin.
|
||||
|
||||
@@ -39,7 +39,7 @@ This plugin is a great way to implement a fast, immersive search experience such
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-search
|
||||
pnpm add @payloadcms/plugin-search@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -31,7 +31,7 @@ This multi-faceted software offers a range of features that will help you manage
|
||||
- **Integrations**: Connects with various tools and services for enhanced workflow and issue management
|
||||
|
||||
<Banner type="info">
|
||||
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/main/packages/plugin-sentry). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-sentry%3A) with as much detail as possible.
|
||||
This plugin is completely open-source and the [source code can be found here](https://github.com/payloadcms/payload/tree/beta/packages/plugin-sentry). If you need help, check out our [Community Help](https://payloadcms.com/community-help). If you think you've found a bug, please [open a new issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%20seo&template=bug_report.md&title=plugin-sentry%3A) with as much detail as possible.
|
||||
</Banner>
|
||||
|
||||
## Installation
|
||||
@@ -39,7 +39,7 @@ This multi-faceted software offers a range of features that will help you manage
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-sentry
|
||||
pnpm add @payloadcms/plugin-sentry@beta
|
||||
```
|
||||
|
||||
## Sentry for Next.js setup
|
||||
|
||||
@@ -37,7 +37,7 @@ To help you visualize what your page might look like in a search engine, a previ
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-seo
|
||||
pnpm add @payloadcms/plugin-seo@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -39,7 +39,7 @@ The beauty of this plugin is the entirety of your application's content and busi
|
||||
Install the plugin using any JavaScript package manager like [Yarn](https://yarnpkg.com), [NPM](https://npmjs.com), or [PNPM](https://pnpm.io):
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/plugin-stripe
|
||||
pnpm add @payloadcms/plugin-stripe@beta
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -147,11 +147,11 @@ But, if you do, and you still want to use an ephemeral filesystem provider, you
|
||||
|
||||
Payload provides a list of official cloud storage adapters for you to use:
|
||||
|
||||
- [Azure Blob Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-azure)
|
||||
- [Google Cloud Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-gcs)
|
||||
- [AWS S3](https://github.com/payloadcms/payload/tree/main/packages/storage-s3)
|
||||
- [Uploadthing](https://github.com/payloadcms/payload/tree/main/packages/storage-uploadthing)
|
||||
- [Vercel Blob Storage](https://github.com/payloadcms/payload/tree/main/packages/storage-vercel-blob)
|
||||
- [Azure Blob Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-azure)
|
||||
- [Google Cloud Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs)
|
||||
- [AWS S3](https://github.com/payloadcms/payload/tree/beta/packages/storage-s3)
|
||||
- [Uploadthing](https://github.com/payloadcms/payload/tree/beta/packages/storage-uploadthing)
|
||||
- [Vercel Blob Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-vercel-blob)
|
||||
|
||||
Follow the docs to configure any one of these storage providers. For local development, it might be handy to simply store uploads on your own computer, and then when it comes to production, simply enable the plugin for the cloud storage vendor of your choice.
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ The following operators are available for use in queries:
|
||||
| `all` | The value must contain all values provided in the comma-delimited list. |
|
||||
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
|
||||
| `near` | For distance related to a [Point Field](../fields/point) comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
|
||||
| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) |
|
||||
| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) |
|
||||
| `within` | For [point fields][../fields/point] to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying---within) |
|
||||
| `intersects` | For [point fields][../fields/point] to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying---intersects) |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
|
||||
@@ -107,9 +107,9 @@ const getPosts = async () => {
|
||||
The `defaultPopulate` property allows you specify which fields to select when populating the collection from another document.
|
||||
This is especially useful for links where only the `slug` is needed instead of the entire document.
|
||||
|
||||
With this feature, you can dramatically reduce the amount of JSON that is populated from [Relationship](/docs/fields/relationship) or [Upload](/docs/fields/upload) fields.
|
||||
With this feature, you can dramatically reduce the amount of JSON that is populated from [Relationship](/docs/beta/fields/relationship) or [Upload](/docs/beta/fields/upload) fields.
|
||||
|
||||
For example, in your content model, you might have a `Link` field which links out to a different page. When you go to retrieve these links, you really only need the `slug` of the page.
|
||||
For example, in your content model, you might have a `Link` field which links out to a different page. When you go to retrieve these links, you really only need the `slug` of the page.
|
||||
|
||||
Loading all of the page content, its related links, and everything else is going to be overkill and will bog down your Payload APIs. Instead, you can define the `defaultPopulate` property on your `Pages` collection, so that when Payload "populates" a related Page, it only selects the `slug` field and therefore returns significantly less JSON:
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ keywords: slatejs, lexical, rich text, json, custom editor, javascript, typescri
|
||||
Payload currently supports two official rich text editors and you can choose either one depending on your needs.
|
||||
|
||||
1. [SlateJS](/docs/rich-text/slate) - stable, backwards-compatible with 1.0
|
||||
2. [Lexical](/docs/lexical/overview) - recommended
|
||||
2. [Lexical](/docs/lexical/overview) - beta, where things will be moving in the future
|
||||
|
||||
These editors are built on an "adapter pattern" which means that you will need to install the editor you'd like to use. Take a look at the docs for the editor you'd like to use for instructions on how to install it.
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ keywords: uploads, images, media, storage, adapters, s3, vercel, google cloud, a
|
||||
Payload offers additional storage adapters to handle file uploads. These adapters allow you to store files in different locations, such as Amazon S3, Vercel Blob Storage, Google Cloud Storage, and more.
|
||||
|
||||
| Service | Package |
|
||||
| -------------------- |-------------------------------------------------------------------------------------------------------------------|
|
||||
| Vercel Blob | [`@payloadcms/storage-vercel-blob`](https://github.com/payloadcms/payload/tree/main/packages/storage-vercel-blob) |
|
||||
| AWS S3 | [`@payloadcms/storage-s3`](https://github.com/payloadcms/payload/tree/main/packages/storage-s3) |
|
||||
| Azure | [`@payloadcms/storage-azure`](https://github.com/payloadcms/payload/tree/main/packages/storage-azure) |
|
||||
| Google Cloud Storage | [`@payloadcms/storage-gcs`](https://github.com/payloadcms/payload/tree/main/packages/storage-gcs) |
|
||||
| Uploadthing | [`@payloadcms/storage-uploadthing`](https://github.com/payloadcms/payload/tree/main/packages/uploadthing) |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| Vercel Blob | [`@payloadcms/storage-vercel-blob`](https://github.com/payloadcms/payload/tree/beta/packages/storage-vercel-blob) |
|
||||
| AWS S3 | [`@payloadcms/storage-s3`](https://github.com/payloadcms/payload/tree/beta/packages/storage-s3) |
|
||||
| Azure | [`@payloadcms/storage-azure`](https://github.com/payloadcms/payload/tree/beta/packages/storage-azure) |
|
||||
| Google Cloud Storage | [`@payloadcms/storage-gcs`](https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs) |
|
||||
|
||||
|
||||
## Vercel Blob Storage
|
||||
[`@payloadcms/storage-vercel-blob`](https://www.npmjs.com/package/@payloadcms/storage-vercel-blob)
|
||||
@@ -22,7 +22,7 @@ Payload offers additional storage adapters to handle file uploads. These adapter
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-vercel-blob
|
||||
pnpm add @payloadcms/storage-vercel-blob@beta
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -43,8 +43,8 @@ export default buildConfig({
|
||||
enabled: true, // Optional, defaults to true
|
||||
// Specify which collections should use Vercel Blob
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[Media.slug]: true,
|
||||
[MediaWithPrefix.slug]: {
|
||||
prefix: 'my-prefix',
|
||||
},
|
||||
},
|
||||
@@ -71,7 +71,7 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-s3
|
||||
pnpm add @payloadcms/storage-s3@beta
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -90,8 +90,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
s3Storage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -119,7 +119,7 @@ See the the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3Clie
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-azure
|
||||
pnpm add @payloadcms/storage-azure@beta
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -137,8 +137,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
azureStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -168,7 +168,7 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-gcs
|
||||
pnpm add @payloadcms/storage-gcs@beta
|
||||
```
|
||||
|
||||
### Usage
|
||||
@@ -186,8 +186,8 @@ export default buildConfig({
|
||||
plugins: [
|
||||
gcsStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': {
|
||||
[mediaSlug]: true,
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
},
|
||||
@@ -218,13 +218,13 @@ export default buildConfig({
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/storage-uploadthing
|
||||
pnpm add @payloadcms/storage-uploadthing@beta
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
- Configure the `collections` object to specify which collections should use uploadthing. The slug _must_ match one of your existing collection slugs and be an `upload` type.
|
||||
- Get a token from Uploadthing and set it as `token` in the `options` object.
|
||||
- Get an API key from Uploadthing and set it as `apiKey` in the `options` object.
|
||||
- `acl` is optional and defaults to `public-read`.
|
||||
|
||||
```ts
|
||||
@@ -233,10 +233,10 @@ export default buildConfig({
|
||||
plugins: [
|
||||
uploadthingStorage({
|
||||
collections: {
|
||||
media: true,
|
||||
[mediaSlug]: true,
|
||||
},
|
||||
options: {
|
||||
token: process.env.UPLOADTHING_TOKEN,
|
||||
apiKey: process.env.UPLOADTHING_SECRET,
|
||||
acl: 'public-read',
|
||||
},
|
||||
}),
|
||||
@@ -248,7 +248,7 @@ export default buildConfig({
|
||||
|
||||
| Option | Description | Default |
|
||||
| ---------------- | ----------------------------------------------- | ------------- |
|
||||
| `token` | Token from Uploadthing. Required. | |
|
||||
| `apiKey` | API key from Uploadthing. Required. | |
|
||||
| `acl` | Access control list for files that are uploaded | `public-read` |
|
||||
| `logLevel` | Log level for Uploadthing | `info` |
|
||||
| `fetch` | Custom fetch function | `fetch` |
|
||||
@@ -261,7 +261,7 @@ If you need to create a custom storage adapter, you can use the [`@payloadcms/pl
|
||||
|
||||
### Installation
|
||||
|
||||
`pnpm add @payloadcms/plugin-cloud-storage`
|
||||
`pnpm add @payloadcms/plugin-cloud-storage@beta`
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const rootParserOptions = {
|
||||
ecmaVersion: 'latest',
|
||||
projectService: {
|
||||
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 40,
|
||||
allowDefaultProject: ['scripts/*.ts', '*.js', '*.mjs', '*.d.ts'],
|
||||
allowDefaultProject: ['scripts/*.ts', '*.js', '*.mjs', '*.spec.ts', '*.d.ts'],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import { Gutter } from '../Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { HeaderNav } from './Nav'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link className={classes.logo} href="/">
|
||||
<Link href="/" className={classes.logo}>
|
||||
<picture>
|
||||
<source
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
|
||||
/>
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={30}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-dark.svg"
|
||||
width={150}
|
||||
height={30}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import { Gutter } from '../Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { HeaderNav } from './Nav'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link className={classes.logo} href="/">
|
||||
<Link href="/" className={classes.logo}>
|
||||
<picture>
|
||||
<source
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
|
||||
/>
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={30}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-dark.svg"
|
||||
width={150}
|
||||
height={30}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
|
||||
@@ -48,12 +48,12 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
||||
|
||||
```ts
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { getPayload } from 'payload'
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '../../payload.config'
|
||||
|
||||
export default async function AccountPage({ searchParams }) {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const payload = await getPayloadHMR({ config: configPromise })
|
||||
const { permissions, user } = await payload.auth({ headers })
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import { Gutter } from '../Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { HeaderNav } from './Nav'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
@@ -14,12 +14,12 @@ export function Header() {
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
/>
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={30}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-dark.svg"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
width={150}
|
||||
/>
|
||||
</picture>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import Link from 'next/link'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
@@ -14,7 +14,7 @@ import classes from './index.module.scss'
|
||||
|
||||
export default async function Account() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { permissions, user } = await payload.auth({ headers })
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
@@ -11,7 +11,7 @@ import classes from './index.module.scss'
|
||||
|
||||
export default async function CreateAccount() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { user } = await payload.auth({ headers })
|
||||
|
||||
if (user) {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
import { Gutter } from '../_components/Gutter'
|
||||
import { RenderParams } from '../_components/RenderParams'
|
||||
import classes from './index.module.scss'
|
||||
import { LoginForm } from './LoginForm'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export default async function Login() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { user } = await payload.auth({ headers })
|
||||
|
||||
if (user) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import Link from 'next/link'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
import { Gutter } from '../_components/Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { LogoutPage } from './LogoutPage'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export default async function Logout() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { user } = await payload.auth({ headers })
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import Link from 'next/link'
|
||||
import { getPayload } from 'payload'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import config from '../../payload.config'
|
||||
@@ -9,7 +9,7 @@ import { HydrateClientUser } from './_components/HydrateClientUser'
|
||||
|
||||
export default async function HomePage() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { permissions, user } = await payload.auth({ headers })
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
import { Gutter } from '../_components/Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { RecoverPasswordForm } from './RecoverPasswordForm'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export default async function RecoverPassword() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { user } = await payload.auth({ headers })
|
||||
|
||||
if (user) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../../payload.config'
|
||||
import { Gutter } from '../_components/Gutter'
|
||||
import classes from './index.module.scss'
|
||||
import { ResetPasswordForm } from './ResetPasswordForm'
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export default async function ResetPassword() {
|
||||
const headers = getHeaders()
|
||||
const payload = await getPayload({ config })
|
||||
const payload = await getPayloadHMR({ config })
|
||||
const { user } = await payload.auth({ headers })
|
||||
|
||||
if (user) {
|
||||
|
||||
6
examples/custom-server/.env.example
Normal file
6
examples/custom-server/.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-custom-server
|
||||
PAYLOAD_SECRET=PAYLOAD_CUSTOM_SERVER_EXAMPLE_SECRET_KEY
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
NEXT_PUBLIC_PAYLOAD_URL=http://localhost:3000
|
||||
PAYLOAD_PUBLIC_SEED=true
|
||||
PAYLOAD_DROP_DATABASE=true
|
||||
5
examples/custom-server/.eslintrc.cjs
Normal file
5
examples/custom-server/.eslintrc.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['plugin:@next/next/recommended', '@payloadcms'],
|
||||
ignorePatterns: ['**/payload-types.ts'],
|
||||
}
|
||||
4
examples/custom-server/.gitignore
vendored
Normal file
4
examples/custom-server/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
build
|
||||
dist
|
||||
node_modules
|
||||
package - lock.json.env.next.vercel
|
||||
1
examples/custom-server/.prettierignore
Normal file
1
examples/custom-server/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
**/payload-types.ts
|
||||
121
examples/custom-server/README.md
Normal file
121
examples/custom-server/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Payload Custom Server Example
|
||||
|
||||
This example demonstrates how to serve your front-end alongside [Payload](https://github.com/payloadcms/payload) in a single Express server. This pattern will cut down on hosting costs and can simplify your deployment process.
|
||||
|
||||
## Quick Start
|
||||
|
||||
To spin up this example locally, follow these steps:
|
||||
|
||||
1. First clone the repo
|
||||
1. Then `cd YOUR_PROJECT_REPO && cp .env.example .env`
|
||||
1. Next `yarn && yarn dev`
|
||||
1. Now `open http://localhost:3000/admin` to access the admin panel
|
||||
1. Login with email `demo@payloadcms.com` and password `demo`
|
||||
|
||||
That's it! Changes made in `./src` will be reflected in your app. See the [Development](#development) section for more details.
|
||||
|
||||
## How it works
|
||||
|
||||
When you use Payload, you plug it into _**your**_ Express server. That's a fundamental difference between Payload and other application frameworks. It means that when you use Payload, you're technically _adding_ Payload to _your_ app, and not building a "Payload app".
|
||||
|
||||
One of the strengths of this pattern is that it lets you do powerful things like integrate your Payload instance directly with your front-end. This will allow you to host Payload alongside a fully dynamic, CMS-integrated website or app on a single, combined server—while still getting all of the benefits of a headless CMS.
|
||||
|
||||
### Express
|
||||
|
||||
In every Payload app is a `server.ts` file in which you instantiate your own Express server and attach Payload to it. This is where you can can add any custom Express middleware or routes you need to serve your front-end. To combine Payload with your framework on the same server, we need to do three main things:
|
||||
|
||||
1. Modify your `server.ts` file to build and serve your front-end using the APIs provided by your framework
|
||||
2. Modify your `package.json` scripts include your framework's build commands
|
||||
3. Use a separate `tsconfig.server.json` file to build the server, because your front-end may require incompatible TypeScript settings
|
||||
|
||||
This example demonstrates how to do this with [Next.js](https://nextjs.org), although the same principles apply to any front-end framework like [Vue](https://vuejs.org), [Nuxt](https://nuxt.com), or [SvelteKit](https://kit.svelte.dev). If your framework does not yet have instructions listed here, please consider contributing them to this example for others to use. To quickly eject Next.js from this example, see the [Eject](#eject) section.
|
||||
|
||||
#### Next.js
|
||||
|
||||
For Next.js apps, your `server.ts` file looks something like this:
|
||||
|
||||
```ts
|
||||
import next from 'next'
|
||||
import nextBuild from 'next/dist/build'
|
||||
|
||||
// Instantiate Express and Payload
|
||||
// ...
|
||||
|
||||
// If building, start the server to build the Next.js app then exit
|
||||
if (process.env.NEXT_BUILD) {
|
||||
app.listen(3000, async () => {
|
||||
await nextBuild(path.join(__dirname, '../'))
|
||||
process.exit()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Attach Next.js routes and start the server
|
||||
const nextApp = next({
|
||||
dev: process.env.NODE_ENV !== 'production',
|
||||
})
|
||||
|
||||
const nextHandler = nextApp.getRequestHandler()
|
||||
|
||||
app.get('*', (req, res) => nextHandler(req, res))
|
||||
|
||||
nextApp.prepare().then(() => {
|
||||
app.listen(3000)
|
||||
})
|
||||
```
|
||||
|
||||
Check out the [server.ts](./src/server.ts) in this repository for a complete working example. You can also see the [Next.js docs](https://nextjs.org/docs/advanced-features/custom-server) for more details.
|
||||
|
||||
Then your `package.json` might look something like this:
|
||||
|
||||
```json
|
||||
// ...
|
||||
"scripts": {
|
||||
// ...
|
||||
"build:payload": "payload build",
|
||||
"build:server": "tsc --project tsconfig.server.json",
|
||||
"build:next": "next build",
|
||||
"build": "yarn build:payload && yarn build:server && yarn build:next",
|
||||
}
|
||||
```
|
||||
|
||||
Check out the [package.json](./src/package.json) in this repository for a complete working example. You can also see the [Next.js docs](https://nextjs.org/docs/api-reference/cli#build) for more details.
|
||||
|
||||
## Eject
|
||||
|
||||
If you prefer another front-end framework or would like to use Payload as a standalone CMS, you can easily eject the front-end from this template. To eject, simply run `yarn eject`. This will uninstall all Next.js related dependencies and delete all files and folders related to the Next.js front-end. It also removes all custom routing from your `server.ts` file and updates your `eslintrc.js`.
|
||||
|
||||
> Note: Your eject script may not work as expected if you've made significant modifications to your project. If you run into any issues, compare your project's dependencies and file structure with this template, see [./src/eject](./src/eject) for full details.
|
||||
|
||||
## Development
|
||||
|
||||
To spin up this example locally, follow the [Quick Start](#quick-start).
|
||||
|
||||
### Seed
|
||||
|
||||
On boot, a seed script is included to scaffold a basic database for you to use as an example. This is done by setting the `PAYLOAD_DROP_DATABASE` and `PAYLOAD_PUBLIC_SEED` environment variables which are included in the `.env.example` by default. You can remove these from your `.env` to prevent this behavior. You can also freshly seed your project at any time by running `yarn seed`. This seed creates an admin user with email `demo@payloadcms.com`, password `demo`, and a `home` page.
|
||||
|
||||
> NOTICE: seeding the database is destructive because it drops your current database to populate a fresh one from the seed template. Only run this command if you are starting a new project or can afford to lose your current data.
|
||||
|
||||
### Conflicting routes
|
||||
|
||||
> In a monorepo when routes are bootstrapped to the same host, they can conflict with Payload's own routes if they have the same name. In our template we've named the Nextjs API routes to `next` to avoid this conflict.
|
||||
>
|
||||
> This can happen with any other routes conflicting with Payload such as `admin` and we recommend using different names for custom routes.
|
||||
> Alternatively you can also rename Payload's own routes via the [configuration](https://payloadcms.com/docs/configuration/overview).
|
||||
|
||||
## Production
|
||||
|
||||
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||
|
||||
1. First, invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
|
||||
1. Then, run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
|
||||
|
||||
### Deployment
|
||||
|
||||
The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also choose to self-host your app, check out the [Deployment](https://payloadcms.com/docs/production/deployment) docs for more details.
|
||||
|
||||
## Questions
|
||||
|
||||
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/payload) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
|
||||
36
examples/custom-server/eject.ts
Normal file
36
examples/custom-server/eject.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// Run this script to eject the front-end from this template
|
||||
// This will remove all template-specific files and directories
|
||||
// See `yarn eject` in `package.json` for the exact command
|
||||
// See `./README.md#eject` for more information
|
||||
|
||||
const files = ['./next.config.js', './next-env.d.ts']
|
||||
const directories = ['./src/app']
|
||||
|
||||
const eject = async (): Promise<void> => {
|
||||
files.forEach((file) => {
|
||||
fs.unlinkSync(path.join(__dirname, file))
|
||||
})
|
||||
|
||||
directories.forEach((directory) => {
|
||||
fs.rm(path.join(__dirname, directory), { recursive: true }, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
})
|
||||
|
||||
// create a new `./src/server.ts` file
|
||||
// use contents from `./src/server.default.ts`
|
||||
const serverFile = path.join(__dirname, './src/server.ts')
|
||||
const serverDefaultFile = path.join(__dirname, './src/server.default.ts')
|
||||
fs.copyFileSync(serverDefaultFile, serverFile)
|
||||
|
||||
// remove `'plugin:@next/next/recommended', ` from `./.eslintrc.js`
|
||||
const eslintConfigFile = path.join(__dirname, './.eslintrc.js')
|
||||
const eslintConfig = fs.readFileSync(eslintConfigFile, 'utf8')
|
||||
const updatedEslintConfig = eslintConfig.replace(`'plugin:@next/next/recommended', `, '')
|
||||
fs.writeFileSync(eslintConfigFile, updatedEslintConfig, 'utf8')
|
||||
}
|
||||
|
||||
eject()
|
||||
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
9
examples/custom-server/next.config.js
Normal file
9
examples/custom-server/next.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
require('dotenv').config()
|
||||
|
||||
module.exports = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
images: {
|
||||
domains: ['localhost', process.env.NEXT_PUBLIC_PAYLOAD_URL],
|
||||
},
|
||||
}
|
||||
6
examples/custom-server/nodemon.json
Normal file
6
examples/custom-server/nodemon.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"watch": ["server.ts"],
|
||||
"exec": "ts-node --project tsconfig.server.json src/server.ts -- -I",
|
||||
"ext": "js ts",
|
||||
"stdin": false
|
||||
}
|
||||
58
examples/custom-server/package.json
Normal file
58
examples/custom-server/package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "payload-example-custom-server",
|
||||
"description": "Payload custom server example.",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||
"seed": "rm -rf media && cross-env PAYLOAD_PUBLIC_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts ts-node src/server.ts",
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc --project tsconfig.server.json",
|
||||
"build:next": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NEXT_BUILD=true node dist/server.js",
|
||||
"build": "cross-env NODE_ENV=production yarn build:payload && yarn build:server && yarn copyfiles && yarn build:next",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"eject": "yarn remove next react react-dom @next/eslint-plugin-next && ts-node eject.ts",
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.17.1",
|
||||
"next": "^13.4.8",
|
||||
"payload": "latest",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^13.1.6",
|
||||
"@payloadcms/eslint-config": "^0.0.2",
|
||||
"@types/escape-html": "^1.0.2",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/node": "18.11.3",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
BIN
examples/custom-server/public/favicon.ico
Normal file
BIN
examples/custom-server/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
15
examples/custom-server/public/favicon.svg
Normal file
15
examples/custom-server/public/favicon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="260" height="260" viewBox="0 0 260 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
path {
|
||||
fill: #0F0F0F;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path d="M120.59 8.5824L231.788 75.6142V202.829L148.039 251.418V124.203L36.7866 57.2249L120.59 8.5824Z" />
|
||||
<path d="M112.123 244.353V145.073L28.2114 193.769L112.123 244.353Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1,7 @@
|
||||
.gutterLeft {
|
||||
padding-left: var(--gutter-h);
|
||||
}
|
||||
|
||||
.gutterRight {
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
33
examples/custom-server/src/app/_components/Gutter/index.tsx
Normal file
33
examples/custom-server/src/app/_components/Gutter/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { forwardRef, Ref } from 'react'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
type Props = {
|
||||
left?: boolean
|
||||
right?: boolean
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
ref?: Ref<HTMLDivElement>
|
||||
}
|
||||
|
||||
export const Gutter: React.FC<Props> = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
const { left = true, right = true, className, children } = props
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={[
|
||||
left && classes.gutterLeft,
|
||||
right && classes.gutterRight,
|
||||
className,
|
||||
classes.gutter,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
Gutter.displayName = 'Gutter'
|
||||
@@ -0,0 +1,180 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import escapeHTML from 'escape-html'
|
||||
import Link from 'next/link'
|
||||
|
||||
type Node = {
|
||||
type: string
|
||||
value?: {
|
||||
url: string
|
||||
alt: string
|
||||
}
|
||||
children?: Node[]
|
||||
url?: string
|
||||
[key: string]: unknown
|
||||
newTab?: boolean
|
||||
}
|
||||
|
||||
export type CustomRenderers = {
|
||||
[key: string]: (args: { node: Node; Serialize: SerializeFunction; index: number }) => JSX.Element // eslint-disable-line
|
||||
}
|
||||
|
||||
type SerializeFunction = React.FC<{
|
||||
content?: Node[]
|
||||
customRenderers?: CustomRenderers
|
||||
}>
|
||||
|
||||
const isText = (value: any): boolean =>
|
||||
typeof value === 'object' && value !== null && typeof value.text === 'string'
|
||||
|
||||
export const Serialize: SerializeFunction = ({ content, customRenderers }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{content?.map((node, i) => {
|
||||
if (isText(node)) {
|
||||
// @ts-expect-error
|
||||
let text = <span dangerouslySetInnerHTML={{ __html: escapeHTML(node.text) }} />
|
||||
|
||||
if (node.bold) {
|
||||
text = <strong key={i}>{text}</strong>
|
||||
}
|
||||
|
||||
if (node.code) {
|
||||
text = <code key={i}>{text}</code>
|
||||
}
|
||||
|
||||
if (node.italic) {
|
||||
text = <em key={i}>{text}</em>
|
||||
}
|
||||
|
||||
if (node.underline) {
|
||||
text = (
|
||||
<span style={{ textDecoration: 'underline' }} key={i}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
if (node.strikethrough) {
|
||||
text = (
|
||||
<span style={{ textDecoration: 'line-through' }} key={i}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return <Fragment key={i}>{text}</Fragment>
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (
|
||||
customRenderers &&
|
||||
customRenderers[node.type] &&
|
||||
typeof customRenderers[node.type] === 'function'
|
||||
) {
|
||||
return customRenderers[node.type]({ node, Serialize, index: i })
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'br':
|
||||
return <br key={i} />
|
||||
|
||||
case 'h1':
|
||||
return (
|
||||
<h1 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h1>
|
||||
)
|
||||
|
||||
case 'h2':
|
||||
return (
|
||||
<h2 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h2>
|
||||
)
|
||||
|
||||
case 'h3':
|
||||
return (
|
||||
<h3 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h3>
|
||||
)
|
||||
|
||||
case 'h4':
|
||||
return (
|
||||
<h4 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h4>
|
||||
)
|
||||
|
||||
case 'h5':
|
||||
return (
|
||||
<h5 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h5>
|
||||
)
|
||||
|
||||
case 'h6':
|
||||
return (
|
||||
<h6 key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</h6>
|
||||
)
|
||||
|
||||
case 'quote':
|
||||
return (
|
||||
<blockquote key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</blockquote>
|
||||
)
|
||||
|
||||
case 'ul':
|
||||
return (
|
||||
<ul key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</ul>
|
||||
)
|
||||
|
||||
case 'ol':
|
||||
return (
|
||||
<ol key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</ol>
|
||||
)
|
||||
|
||||
case 'li':
|
||||
return (
|
||||
<li key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</li>
|
||||
)
|
||||
|
||||
case 'link':
|
||||
return (
|
||||
<Link
|
||||
href={escapeHTML(node.url)}
|
||||
key={i}
|
||||
{...(node.newTab
|
||||
? {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</Link>
|
||||
)
|
||||
|
||||
default:
|
||||
return (
|
||||
<p key={i}>
|
||||
<Serialize content={node.children} customRenderers={customRenderers} />
|
||||
</p>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.richText {
|
||||
text-align: center; // hack but whatever
|
||||
max-width: 1000px;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { CustomRenderers, Serialize as SerializeContent } from './Serialize'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export const RichText: React.FC<{
|
||||
className?: string
|
||||
content: any
|
||||
customRenderers?: CustomRenderers
|
||||
}> = ({ className, content, customRenderers }) => {
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[classes.richText, className].filter(Boolean).join(' ')}>
|
||||
<SerializeContent content={content} customRenderers={customRenderers} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
118
examples/custom-server/src/app/app.scss
Normal file
118
examples/custom-server/src/app/app.scss
Normal file
@@ -0,0 +1,118 @@
|
||||
$breakpoint: 1000px;
|
||||
|
||||
:root {
|
||||
--max-width: 1600px;
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-rgb: 255, 255, 255;
|
||||
--block-spacing: 2rem;
|
||||
--gutter-h: 4rem;
|
||||
--base: 1rem;
|
||||
|
||||
@media (max-width: $breakpoint) {
|
||||
--block-spacing: 1rem;
|
||||
--gutter-h: 2rem;
|
||||
--base: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-rgb: 7, 7, 7;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 20px;
|
||||
line-height: 1.5;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Open Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
|
||||
@media (max-width: $breakpoint) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: rgb(var(--background-rgb));
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4.5rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 2.5rem 0;
|
||||
|
||||
@media (max-width: $breakpoint) {
|
||||
font-size: 3rem;
|
||||
margin: 0 0 1.5rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 3.5rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 2.5rem 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 2rem 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
16
examples/custom-server/src/app/layout.module.scss
Normal file
16
examples/custom-server/src/app/layout.module.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.body {
|
||||
padding: 6rem 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 200px;
|
||||
}
|
||||
36
examples/custom-server/src/app/layout.tsx
Normal file
36
examples/custom-server/src/app/layout.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import './app.scss'
|
||||
|
||||
import classes from './layout.module.scss'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Payload Custom Server',
|
||||
description: 'Serve Payload alongside any front-end framework.',
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={classes.body}>
|
||||
<header className={classes.header}>
|
||||
<Link href="https://payloadcms.com" target="_blank" rel="noopener noreferrer">
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
/>
|
||||
<img
|
||||
className={classes.logo}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
</header>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
10
examples/custom-server/src/app/next/test-get/route.ts
Normal file
10
examples/custom-server/src/app/next/test-get/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* The Next.js API routes can conflict with Payload's own routes if they share the same path
|
||||
* To avoid this you can customise the path of Payload or the API route of Nextjs as we've done here
|
||||
* See readme: https://github.com/payloadcms/payload/tree/main/examples/custom-server#conflicting-routes
|
||||
* */
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
return NextResponse.json({ success: true })
|
||||
}
|
||||
10
examples/custom-server/src/app/next/test-post/route.ts
Normal file
10
examples/custom-server/src/app/next/test-post/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* The Next.js API routes can conflict with Payload's own routes if they share the same path
|
||||
* To avoid this you can customise the path of Payload or the API route of Nextjs as we've done here
|
||||
* See readme: https://github.com/payloadcms/payload/tree/main/examples/custom-server#conflicting-routes
|
||||
* */
|
||||
export async function POST(): Promise<NextResponse> {
|
||||
return NextResponse.json({ success: true })
|
||||
}
|
||||
17
examples/custom-server/src/app/page.module.scss
Normal file
17
examples/custom-server/src/app/page.module.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
39
examples/custom-server/src/app/page.tsx
Normal file
39
examples/custom-server/src/app/page.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
import { getPayloadClient } from '../getPayload'
|
||||
import { Page } from './../payload-types'
|
||||
import { Gutter } from './_components/Gutter'
|
||||
import { RichText } from './_components/RichText'
|
||||
|
||||
import classes from './page.module.scss'
|
||||
|
||||
export default async function Home() {
|
||||
const payload = await getPayloadClient()
|
||||
const { docs } = await payload.find({
|
||||
collection: 'pages',
|
||||
where: {
|
||||
slug: {
|
||||
equals: 'home',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const home = docs?.[0] as Page
|
||||
|
||||
if (!home) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<main className={classes.main}>
|
||||
<Gutter>
|
||||
<div className={classes.body}>
|
||||
<RichText content={home.richText} />
|
||||
</div>
|
||||
</Gutter>
|
||||
</main>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
39
examples/custom-server/src/collections/Pages.ts
Normal file
39
examples/custom-server/src/collections/Pages.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
import formatSlug from '../utilities/formatSlug'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
label: 'Content',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
type: 'text',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [formatSlug('title')],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default Pages
|
||||
17
examples/custom-server/src/components/BeforeLogin/index.tsx
Normal file
17
examples/custom-server/src/components/BeforeLogin/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
const BeforeLogin: React.FC = () => {
|
||||
if (process.env.PAYLOAD_PUBLIC_SEED === 'true') {
|
||||
return (
|
||||
<p>
|
||||
{'Log in with the email '}
|
||||
<strong>demo@payloadcms.com</strong>
|
||||
{' and the password '}
|
||||
<strong>demo</strong>.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default BeforeLogin
|
||||
54
examples/custom-server/src/getPayload.ts
Normal file
54
examples/custom-server/src/getPayload.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
import type { Payload } from 'payload'
|
||||
import payload from 'payload'
|
||||
import type { InitOptions } from 'payload/config'
|
||||
|
||||
import { seed as seedData } from './seed'
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
let cached = (global as any).payload
|
||||
|
||||
if (!cached) {
|
||||
cached = (global as any).payload = { client: null, promise: null }
|
||||
}
|
||||
|
||||
interface Args {
|
||||
initOptions?: Partial<InitOptions>
|
||||
seed?: boolean
|
||||
}
|
||||
|
||||
export const getPayloadClient = async ({ initOptions, seed }: Args = {}): Promise<Payload> => {
|
||||
if (!process.env.PAYLOAD_SECRET) {
|
||||
throw new Error('PAYLOAD_SECRET environment variable is missing')
|
||||
}
|
||||
|
||||
if (cached.client) {
|
||||
return cached.client
|
||||
}
|
||||
|
||||
if (!cached.promise) {
|
||||
cached.promise = payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
local: initOptions?.express ? false : true,
|
||||
...(initOptions || {}),
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
process.env.PAYLOAD_DROP_DATABASE = seed ? 'true' : 'false'
|
||||
cached.client = await cached.promise
|
||||
|
||||
if (seed) {
|
||||
await seedData(payload)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
cached.promise = null
|
||||
throw e
|
||||
}
|
||||
|
||||
return cached.client
|
||||
}
|
||||
77
examples/custom-server/src/payload-types.ts
Normal file
77
examples/custom-server/src/payload-types.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
globals: {}
|
||||
}
|
||||
export interface Page {
|
||||
id: string
|
||||
title: string
|
||||
richText?: {
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
slug?: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface User {
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
salt?: string
|
||||
hash?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface PayloadPreference {
|
||||
id: string
|
||||
user: {
|
||||
relationTo: 'users'
|
||||
value: string | User
|
||||
}
|
||||
key?: string
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface PayloadMigration {
|
||||
id: string
|
||||
name?: string
|
||||
batch?: number
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes {
|
||||
collections: {
|
||||
pages: Page
|
||||
users: User
|
||||
'payload-preferences': PayloadPreference
|
||||
'payload-migrations': PayloadMigration
|
||||
}
|
||||
}
|
||||
}
|
||||
32
examples/custom-server/src/payload.config.ts
Normal file
32
examples/custom-server/src/payload.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
import { Pages } from './collections/Pages'
|
||||
import BeforeLogin from './components/BeforeLogin'
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || '',
|
||||
collections: [Pages],
|
||||
admin: {
|
||||
bundler: webpackBundler(),
|
||||
components: {
|
||||
beforeLogin: [BeforeLogin],
|
||||
},
|
||||
},
|
||||
editor: slateEditor({}),
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}),
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
})
|
||||
93
examples/custom-server/src/seed/index.ts
Normal file
93
examples/custom-server/src/seed/index.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
export const seed = async (payload: Payload): Promise<void> => {
|
||||
// create admin
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'demo@payloadcms.com',
|
||||
password: 'demo',
|
||||
},
|
||||
})
|
||||
|
||||
// create home page
|
||||
await Promise.all([
|
||||
await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'Home',
|
||||
richText: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'Payload Custom Server Example',
|
||||
},
|
||||
],
|
||||
type: 'h1',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'This is an example of how to host ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'custom',
|
||||
url: 'https://payloadcms.com',
|
||||
children: [
|
||||
{
|
||||
text: 'Payload',
|
||||
},
|
||||
],
|
||||
newTab: true,
|
||||
},
|
||||
{
|
||||
text: ' alongside your front-end by sharing a single Express server. You are currently browsing a ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'custom',
|
||||
url: 'https://nextjs.org',
|
||||
children: [
|
||||
{
|
||||
text: 'Next.js',
|
||||
},
|
||||
],
|
||||
newTab: true,
|
||||
},
|
||||
{
|
||||
text: ' app, but you can easily swap in any framework you like—check out the ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'custom',
|
||||
url: 'http://github.com/payloadcms/payload/tree/main/examples/custom-server',
|
||||
children: [
|
||||
{
|
||||
text: 'README.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: ' for instructions on how to do this. ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'custom',
|
||||
url: 'http://localhost:3000/admin',
|
||||
children: [
|
||||
{
|
||||
text: 'Click here',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: ' to navigate to the admin panel and login.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
])
|
||||
}
|
||||
35
examples/custom-server/src/server.default.ts
Normal file
35
examples/custom-server/src/server.default.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
|
||||
// This file is used to replace `server.ts` when ejecting i.e. `yarn eject`
|
||||
// See `../eject.ts` for exact details on how this file is used
|
||||
// See `./README.md#eject` for more information
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
import express from 'express'
|
||||
|
||||
import { getPayloadClient } from './getPayload'
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 3000
|
||||
|
||||
const start = async (): Promise<void> => {
|
||||
const payload = await getPayloadClient({
|
||||
initOptions: {
|
||||
express: app,
|
||||
onInit: async (newPayload) => {
|
||||
newPayload.logger.info(`Payload Admin URL: ${newPayload.getAdminURL()}`)
|
||||
},
|
||||
},
|
||||
seed: process.env.PAYLOAD_PUBLIC_SEED === 'true',
|
||||
})
|
||||
|
||||
app.listen(PORT, async () => {
|
||||
payload.logger.info(`App URL: ${process.env.PAYLOAD_PUBLIC_SERVER_URL}`)
|
||||
})
|
||||
}
|
||||
|
||||
start()
|
||||
56
examples/custom-server/src/server.ts
Normal file
56
examples/custom-server/src/server.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import dotenv from 'dotenv'
|
||||
import next from 'next'
|
||||
import nextBuild from 'next/dist/build'
|
||||
import path from 'path'
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
import express from 'express'
|
||||
|
||||
import { getPayloadClient } from './getPayload'
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 3000
|
||||
|
||||
const start = async (): Promise<void> => {
|
||||
const payload = await getPayloadClient({
|
||||
initOptions: {
|
||||
express: app,
|
||||
onInit: async (newPayload) => {
|
||||
newPayload.logger.info(`Payload Admin URL: ${newPayload.getAdminURL()}`)
|
||||
},
|
||||
},
|
||||
seed: process.env.PAYLOAD_PUBLIC_SEED === 'true',
|
||||
})
|
||||
|
||||
if (process.env.NEXT_BUILD) {
|
||||
app.listen(PORT, async () => {
|
||||
payload.logger.info(`Next.js is now building...`)
|
||||
// @ts-expect-error
|
||||
await nextBuild(path.join(__dirname, '..'))
|
||||
process.exit()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const nextApp = next({
|
||||
dev: process.env.NODE_ENV !== 'production',
|
||||
})
|
||||
|
||||
const nextHandler = nextApp.getRequestHandler()
|
||||
|
||||
app.use((req, res) => nextHandler(req, res))
|
||||
|
||||
nextApp.prepare().then(() => {
|
||||
payload.logger.info('Next.js started')
|
||||
|
||||
app.listen(PORT, async () => {
|
||||
payload.logger.info(`Next.js App URL: ${process.env.PAYLOAD_PUBLIC_SERVER_URL}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
start()
|
||||
24
examples/custom-server/src/utilities/formatSlug.ts
Normal file
24
examples/custom-server/src/utilities/formatSlug.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { FieldHook } from 'payload/types'
|
||||
|
||||
const format = (val: string): string =>
|
||||
val
|
||||
.replace(/ /g, '-')
|
||||
.replace(/[^\w-/]+/g, '')
|
||||
.toLowerCase()
|
||||
|
||||
const formatSlug =
|
||||
(fallback: string): FieldHook =>
|
||||
({ value, originalDoc, data }) => {
|
||||
if (typeof value === 'string') {
|
||||
return format(value)
|
||||
}
|
||||
const fallbackData = data?.[fallback] || originalDoc?.[fallback]
|
||||
|
||||
if (fallbackData && typeof fallbackData === 'string') {
|
||||
return format(fallbackData)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
export default formatSlug
|
||||
39
examples/custom-server/tsconfig.json
Normal file
39
examples/custom-server/tsconfig.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"jsx": "preserve",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true,
|
||||
"swc": true
|
||||
}
|
||||
}
|
||||
10
examples/custom-server/tsconfig.server.json
Normal file
10
examples/custom-server/tsconfig.server.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"noEmit": false,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src/server.ts", "src/payload.config.ts"]
|
||||
}
|
||||
8554
examples/custom-server/yarn.lock
Normal file
8554
examples/custom-server/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
|
||||
import type { MainMenu } from '../../../payload-types'
|
||||
|
||||
import { MainMenu } from '../../../payload-types'
|
||||
import { CMSLink } from '../CMSLink'
|
||||
import { Gutter } from '../Gutter'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
export async function Header() {
|
||||
@@ -20,17 +20,17 @@ export async function Header() {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link className={classes.logo} href="/">
|
||||
<Link href="/" className={classes.logo}>
|
||||
<picture>
|
||||
<source
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
|
||||
/>
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={30}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-dark.svg"
|
||||
width={150}
|
||||
height={30}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import type { PayloadAdminBarProps, PayloadMeUser } from 'payload-admin-bar'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import type { MainMenu } from '../../payload-types'
|
||||
import { PayloadAdminBarProps, PayloadMeUser } from 'payload-admin-bar'
|
||||
|
||||
import { MainMenu } from '../../payload-types'
|
||||
import { AdminBar } from '../AdminBar'
|
||||
import { CMSLink } from '../CMSLink'
|
||||
import { Gutter } from '../Gutter'
|
||||
|
||||
import classes from './index.module.scss'
|
||||
|
||||
type HeaderBarProps = {
|
||||
@@ -19,17 +18,17 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ children }) => {
|
||||
return (
|
||||
<header className={classes.header}>
|
||||
<Gutter className={classes.wrap}>
|
||||
<Link className={classes.logo} href="/">
|
||||
<Link href="/" className={classes.logo}>
|
||||
<picture>
|
||||
<source
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcSet="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
|
||||
/>
|
||||
<Image
|
||||
alt="Payload Logo"
|
||||
height={30}
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-dark.svg"
|
||||
width={150}
|
||||
height={30}
|
||||
alt="Payload Logo"
|
||||
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-dark.svg"
|
||||
/>
|
||||
</picture>
|
||||
</Link>
|
||||
@@ -40,12 +39,12 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ children }) => {
|
||||
}
|
||||
|
||||
export const Header: React.FC<{
|
||||
adminBarProps: PayloadAdminBarProps
|
||||
globals: {
|
||||
mainMenu: MainMenu
|
||||
}
|
||||
adminBarProps: PayloadAdminBarProps
|
||||
}> = (props) => {
|
||||
const { adminBarProps, globals } = props
|
||||
const { globals, adminBarProps } = props
|
||||
|
||||
const [user, setUser] = useState<PayloadMeUser>()
|
||||
|
||||
@@ -57,7 +56,7 @@ export const Header: React.FC<{
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AdminBar adminBarProps={adminBarProps} setUser={setUser} user={user} />
|
||||
<AdminBar adminBarProps={adminBarProps} user={user} setUser={setUser} />
|
||||
<HeaderBar>
|
||||
{hasNavItems && (
|
||||
<nav className={classes.nav}>
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
import type { Page } from '../payload-types'
|
||||
|
||||
export const home: Partial<Page> = {
|
||||
title: 'Home Page',
|
||||
slug: 'home',
|
||||
_status: 'published',
|
||||
richText: [
|
||||
{
|
||||
children: [
|
||||
{ text: 'This is a ' },
|
||||
{ type: 'link', children: [{ text: '' }], newTab: true, url: 'https://nextjs.org/' },
|
||||
{ type: 'link', newTab: true, url: 'https://nextjs.org/', children: [{ text: '' }] },
|
||||
{ text: '' },
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'Next.js' }],
|
||||
linkType: 'custom',
|
||||
newTab: true,
|
||||
url: 'https://nextjs.org/',
|
||||
newTab: true,
|
||||
children: [{ text: 'Next.js' }],
|
||||
},
|
||||
{ text: " app made explicitly for Payload's " },
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'Draft Preview Example' }],
|
||||
newTab: true,
|
||||
url: 'https://github.com/payloadcms/payload/tree/main/examples/redirects',
|
||||
children: [{ text: '' }],
|
||||
},
|
||||
{ text: '' },
|
||||
{
|
||||
type: 'link',
|
||||
linkType: 'custom',
|
||||
newTab: true,
|
||||
url: 'https://github.com/payloadcms/payload/tree/main/examples/draft-preview/payload',
|
||||
children: [{ text: 'Draft Preview Example' }],
|
||||
},
|
||||
{ text: '. This example demonstrates how to implement draft preview into Payload using ' },
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'Drafts' }],
|
||||
newTab: true,
|
||||
url: 'https://payloadcms.com/docs/versions/drafts#drafts',
|
||||
children: [{ text: 'Drafts' }],
|
||||
},
|
||||
{ text: '.' },
|
||||
],
|
||||
@@ -39,31 +47,30 @@ export const home: Partial<Page> = {
|
||||
children: [
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'Log in to the admin panel' }],
|
||||
linkType: 'custom',
|
||||
newTab: true,
|
||||
url: 'http://localhost:3000/admin',
|
||||
newTab: true,
|
||||
children: [{ text: 'Log in to the admin panel' }],
|
||||
},
|
||||
{ text: ' and refresh this page to see the ' },
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'Payload Admin Bar' }],
|
||||
linkType: 'custom',
|
||||
newTab: true,
|
||||
url: 'https://github.com/payloadcms/payload-admin-bar',
|
||||
children: [{ text: 'Payload Admin Bar' }],
|
||||
},
|
||||
{
|
||||
text: ' appear at the top of this site. This will allow you to seamlessly navigate between the two apps. Then, navigate to the ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'example page' }],
|
||||
linkType: 'custom',
|
||||
url: 'http://localhost:3001/example-page',
|
||||
children: [{ text: 'example page' }],
|
||||
},
|
||||
{ text: ' to see how we control access to draft content. ' },
|
||||
],
|
||||
},
|
||||
],
|
||||
title: 'Home Page',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
DATABASE_URI=mongodb://127.0.0.1/payload-example-email
|
||||
MONGODB_URI=mongodb://127.0.0.1/payload-example-email
|
||||
PAYLOAD_SECRET=
|
||||
NODE_ENV=development
|
||||
PAYLOAD_SECRET=PAYLOAD_EMAIL_EXAMPLE_SECRET_KEY
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
|
||||
|
||||
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@payloadcms'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic",
|
||||
"pragmaFrag": "React.Fragment",
|
||||
"throwIfNamespace": true,
|
||||
"development": false,
|
||||
"useBuiltins": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
@@ -7,31 +7,30 @@ This example demonstrates how to integrate email functionality into Payload.
|
||||
To spin up this example locally, follow these steps:
|
||||
|
||||
1. Clone this repo
|
||||
2. `cp .env.example .env` to copy the example environment variables
|
||||
3. `pnpm install && pnpm dev` to install dependencies and start the dev server
|
||||
2. `cd` into this directory and run `yarn` or `npm install`
|
||||
3. `cp .env.example .env` to copy the example environment variables
|
||||
4. `yarn dev` or `npm run dev` to start the server and seed the database
|
||||
5. open `http://localhost:3000/admin` to access the admin panel
|
||||
5. `open http://localhost:8000/admin` to access the admin panel
|
||||
6. Create your first user
|
||||
|
||||
## How it works
|
||||
|
||||
Email functionality in Payload is configured using adapters. The recommended adapter for most use cases is the [@payloadcms/email-nodemailer](https://www.npmjs.com/package/@payloadcms/email-nodemailer) package.
|
||||
Payload utilizes [NodeMailer](https://nodemailer.com/about/) for email functionality. Once you add your email configuration to `payload.init()`, you send email from anywhere in your application just by calling `payload.sendEmail({})`.
|
||||
|
||||
To enable email, pass your adapter configuration to the `email` property in the Payload Config. This allows Payload to send auth-related emails for password resets, new user verifications, and other email needs.
|
||||
|
||||
1. In the Payload Config file, add your email adapter to the `email` property. For example, the `@payloadcms/email-nodemailer` adapter can be configured for SMTP, SendGrid, or other supported transports. During development, if no configuration is provided, Payload will use a mock service via [ethereal.email](ethereal.email).
|
||||
1. Navigate to `src/server.ts` - this is where your email config gets passed to Payload
|
||||
2. Open `src/email/transport.ts` - here we are defining the email config. You can use an env variable to switch between the mock email transport and live email service.
|
||||
|
||||
Now we can start sending email!
|
||||
|
||||
2. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
|
||||
3. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
|
||||
|
||||
Let's not forget our authentication emails...
|
||||
|
||||
3. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
|
||||
4. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
|
||||
|
||||
Speaking of customization...
|
||||
|
||||
4. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
|
||||
5. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
|
||||
|
||||
That's all you need, now you can go ahead and test out this repo by creating a new `user` or `newsletter-signup` and see the email integration in action.
|
||||
|
||||
@@ -41,10 +40,10 @@ To spin up this example locally, follow the [Quick Start](#quick-start).
|
||||
|
||||
## Production
|
||||
|
||||
To run Payload in production, you need to build and start the Admin panel. To do so, follow these steps:
|
||||
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||
|
||||
1. Invoke the `next build` script by running `pnpm build` or `npm run build` in your project root. This creates a `.next` directory with a production-ready admin bundle.
|
||||
1. Finally run `pnpm start` or `npm run start` to run Node in production and serve Payload from the `.build` directory.
|
||||
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
|
||||
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
|
||||
|
||||
### Deployment
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user