diff --git a/docs/plugins/seo.mdx b/docs/plugins/seo.mdx
index 20a377338a..72355528af 100644
--- a/docs/plugins/seo.mdx
+++ b/docs/plugins/seo.mdx
@@ -68,7 +68,7 @@ const config = buildConfig({
'pages',
],
uploadsCollection: 'media',
- generateTitle: ({ doc }) => `Website.com — ${doc.title.value}`,
+ generateTitle: ({ doc }) => `Website.com — ${doc.title}`,
generateDescription: ({ doc }) => doc.excerpt
})
]
@@ -119,7 +119,7 @@ A function that allows you to return any meta title, including from document's c
{
// ...
seoPlugin({
- generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title?.value}`,
+ generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title}`,
})
}
```
@@ -133,7 +133,7 @@ A function that allows you to return any meta description, including from docume
{
// ...
seoPlugin({
- generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt?.value,
+ generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt,
})
}
```
@@ -147,7 +147,7 @@ A function that allows you to return any meta image, including from document's c
{
// ...
seoPlugin({
- generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage?.value,
+ generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage,
})
}
```
@@ -162,7 +162,7 @@ A function called by the search preview component to display the actual URL of y
// ...
seoPlugin({
generateURL: ({ ...docInfo, doc, locale }) =>
- `https://yoursite.com/${collection?.slug}/${doc?.slug?.value}`,
+ `https://yoursite.com/${collection?.slug}/${doc?.slug}`,
})
}
```
@@ -200,6 +200,54 @@ seoPlugin({
})
```
+## Direct use of fields
+
+There is the option to directly import any of the fields from the plugin so that you can include them anywhere as needed.
+
+
+ You will still need to configure the plugin in the Payload config in order to configure the generation functions.
+ Since these fields are imported and used directly, they don't have access to the plugin config so they may need additional arguments to work the same way.
+
+
+```ts
+import { MetaDescriptionField, MetaImageField, MetaTitleField, OverviewField, PreviewField } from '@payloadcms/plugin-seo/fields'
+
+// Used as fields
+MetaImageField({
+ // the upload collection slug
+ relationTo: 'media',
+
+ // if the `generateImage` function is configured
+ hasGenerateFn: true,
+})
+
+MetaDescriptionField({
+ // if the `generateDescription` function is configured
+ hasGenerateFn: true,
+})
+
+MetaTitleField({
+ // if the `generateTitle` function is configured
+ hasGenerateFn: true,
+})
+
+PreviewField({
+ // if the `generateUrl` function is configured
+ hasGenerateFn: true,
+
+ // field paths to match the target field for data
+ titlePath: 'meta.title',
+ descriptionPath: 'meta.description',
+})
+
+OverviewField({
+ // field paths to match the target field for data
+ titlePath: 'meta.title',
+ descriptionPath: 'meta.description',
+ imagePath: 'meta.image',
+})
+```
+
## TypeScript
All types can be directly imported:
@@ -213,6 +261,18 @@ import {
} from '@payloadcms/plugin-seo/types';
```
+You can then pass the collections from your generated Payload types into the generation types, for example:
+
+```ts
+import { Page } from './payload-types.ts';
+
+import { GenerateTitle } from '@payloadcms/plugin-seo/types';
+
+const generateTitle: GenerateTitle = async ({ doc, locale }) => {
+ return `Website.com — ${doc?.title}`
+}
+```
+
## Examples
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) 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) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end.
diff --git a/packages/plugin-seo/package.json b/packages/plugin-seo/package.json
index a7157daca8..e7b8611877 100644
--- a/packages/plugin-seo/package.json
+++ b/packages/plugin-seo/package.json
@@ -29,6 +29,11 @@
"import": "./src/exports/types.ts",
"types": "./src/exports/types.ts",
"default": "./src/exports/types.ts"
+ },
+ "./fields": {
+ "import": "./src/exports/fields.ts",
+ "types": "./src/exports/fields.ts",
+ "default": "./src/exports/fields.ts"
}
},
"main": "./src/index.tsx",
@@ -73,6 +78,11 @@
"import": "./dist/exports/types.js",
"types": "./dist/exports/types.d.ts",
"default": "./dist/exports/types.js"
+ },
+ "./fields": {
+ "import": "./dist/exports/fields.js",
+ "types": "./dist/exports/fields.d.ts",
+ "default": "./dist/exports/fields.js"
}
},
"main": "./dist/index.js",
diff --git a/packages/plugin-seo/src/exports/fields.ts b/packages/plugin-seo/src/exports/fields.ts
new file mode 100644
index 0000000000..37d469f30a
--- /dev/null
+++ b/packages/plugin-seo/src/exports/fields.ts
@@ -0,0 +1,5 @@
+export { MetaDescriptionField } from '../fields/MetaDescription/index.js'
+export { MetaImageField } from '../fields/MetaImage/index.js'
+export { MetaTitleField } from '../fields/MetaTitle/index.js'
+export { OverviewField } from '../fields/Overview/index.js'
+export { PreviewField } from '../fields/Preview/index.js'
diff --git a/packages/plugin-seo/src/fields/MetaDescription.tsx b/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx
similarity index 91%
rename from packages/plugin-seo/src/fields/MetaDescription.tsx
rename to packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx
index 49284ea1d2..71cc934cce 100644
--- a/packages/plugin-seo/src/fields/MetaDescription.tsx
+++ b/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx
@@ -14,11 +14,11 @@ import {
} from '@payloadcms/ui'
import React, { useCallback } from 'react'
-import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
-import type { GenerateDescription } from '../types.js'
+import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
+import type { GenerateDescription } from '../../types.js'
-import { defaults } from '../defaults.js'
-import { LengthIndicator } from '../ui/LengthIndicator.js'
+import { defaults } from '../../defaults.js'
+import { LengthIndicator } from '../../ui/LengthIndicator.js'
const { maxLength, minLength } = defaults.description
@@ -28,8 +28,8 @@ type MetaDescriptionProps = FormFieldBase & {
path: string
}
-export const MetaDescription: React.FC = (props) => {
- const { CustomLabel, hasGenerateDescriptionFn, label, labelProps, path, required } = props
+export const MetaDescriptionComponent: React.FC = (props) => {
+ const { CustomLabel, hasGenerateDescriptionFn, label, labelProps, required } = props
const { path: pathFromContext } = useFieldProps()
const { t } = useTranslation()
@@ -39,7 +39,7 @@ export const MetaDescription: React.FC = (props) => {
const docInfo = useDocumentInfo()
const field: FieldType = useField({
- path,
+ path: pathFromContext,
} as Options)
const { errorMessage, setValue, showError, value } = field
diff --git a/packages/plugin-seo/src/fields/MetaDescription/index.ts b/packages/plugin-seo/src/fields/MetaDescription/index.ts
new file mode 100644
index 0000000000..983ffd577f
--- /dev/null
+++ b/packages/plugin-seo/src/fields/MetaDescription/index.ts
@@ -0,0 +1,35 @@
+import type { TextareaField } from 'payload'
+
+import { withMergedProps } from '@payloadcms/ui/shared'
+
+import { MetaDescriptionComponent } from './MetaDescriptionComponent.js'
+
+interface FieldFunctionProps {
+ /**
+ * Tell the component if the generate function is available as configured in the plugin config
+ */
+ hasGenerateFn?: boolean
+ overrides?: Partial
+}
+
+type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => TextareaField
+
+export const MetaDescriptionField: FieldFunction = ({ hasGenerateFn = false, overrides }) => {
+ return {
+ name: 'description',
+ type: 'textarea',
+ admin: {
+ components: {
+ Field: withMergedProps({
+ Component: MetaDescriptionComponent,
+ sanitizeServerOnlyProps: true,
+ toMergeIntoProps: {
+ hasGenerateDescriptionFn: hasGenerateFn,
+ },
+ }),
+ },
+ },
+ localized: true,
+ ...((overrides as unknown as TextareaField) ?? {}),
+ }
+}
diff --git a/packages/plugin-seo/src/fields/MetaImage.tsx b/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx
similarity index 91%
rename from packages/plugin-seo/src/fields/MetaImage.tsx
rename to packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx
index dca98e75b3..b3b11c605d 100644
--- a/packages/plugin-seo/src/fields/MetaImage.tsx
+++ b/packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx
@@ -8,26 +8,28 @@ import {
useConfig,
useDocumentInfo,
useField,
+ useFieldProps,
useForm,
useLocale,
useTranslation,
} from '@payloadcms/ui'
import React, { useCallback } from 'react'
-import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
-import type { GenerateImage } from '../types.js'
+import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
+import type { GenerateImage } from '../../types.js'
-import { Pill } from '../ui/Pill.js'
+import { Pill } from '../../ui/Pill.js'
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type MetaImageProps = UploadInputProps & {
hasGenerateImageFn: boolean
}
-export const MetaImage: React.FC = (props) => {
+export const MetaImageComponent: React.FC = (props) => {
const { CustomLabel, hasGenerateImageFn, label, labelProps, relationTo, required } = props || {}
+ const { path: pathFromContext } = useFieldProps()
- const field: FieldType = useField(props as Options)
+ const field: FieldType = useField({ ...props, path: pathFromContext } as Options)
const { t } = useTranslation()
diff --git a/packages/plugin-seo/src/fields/MetaImage/index.ts b/packages/plugin-seo/src/fields/MetaImage/index.ts
new file mode 100644
index 0000000000..0333e98e4b
--- /dev/null
+++ b/packages/plugin-seo/src/fields/MetaImage/index.ts
@@ -0,0 +1,39 @@
+import type { UploadField } from 'payload'
+
+import { withMergedProps } from '@payloadcms/ui/shared'
+
+import { MetaImageComponent } from './MetaImageComponent.js'
+
+interface FieldFunctionProps {
+ /**
+ * Tell the component if the generate function is available as configured in the plugin config
+ */
+ hasGenerateFn?: boolean
+ overrides?: Partial
+ relationTo: string
+}
+
+type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => UploadField
+
+export const MetaImageField: FieldFunction = ({ hasGenerateFn = false, overrides, relationTo }) => {
+ return {
+ name: 'image',
+ type: 'upload',
+ admin: {
+ components: {
+ Field: withMergedProps({
+ Component: MetaImageComponent,
+ sanitizeServerOnlyProps: true,
+ toMergeIntoProps: {
+ hasGenerateImageFn: hasGenerateFn,
+ },
+ }),
+ },
+ description: 'Maximum upload file size: 12MB. Recommended file size for images is <500KB.',
+ },
+ label: 'Meta Image',
+ localized: true,
+ relationTo,
+ ...((overrides as unknown as UploadField) ?? {}),
+ }
+}
diff --git a/packages/plugin-seo/src/fields/MetaTitle.tsx b/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx
similarity index 90%
rename from packages/plugin-seo/src/fields/MetaTitle.tsx
rename to packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx
index 486927d1b6..3b071ea580 100644
--- a/packages/plugin-seo/src/fields/MetaTitle.tsx
+++ b/packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx
@@ -14,12 +14,12 @@ import {
} from '@payloadcms/ui'
import React, { useCallback } from 'react'
-import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
-import type { GenerateTitle } from '../types.js'
+import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
+import type { GenerateTitle } from '../../types.js'
-import { defaults } from '../defaults.js'
-import { LengthIndicator } from '../ui/LengthIndicator.js'
-import './index.scss'
+import { defaults } from '../../defaults.js'
+import { LengthIndicator } from '../../ui/LengthIndicator.js'
+import '../index.scss'
const { maxLength, minLength } = defaults.title
@@ -28,14 +28,14 @@ type MetaTitleProps = FormFieldBase & {
hasGenerateTitleFn: boolean
}
-export const MetaTitle: React.FC = (props) => {
- const { CustomLabel, hasGenerateTitleFn, label, labelProps, path, required } = props || {}
+export const MetaTitleComponent: React.FC = (props) => {
+ const { CustomLabel, hasGenerateTitleFn, label, labelProps, required } = props || {}
const { path: pathFromContext } = useFieldProps()
const { t } = useTranslation()
const field: FieldType = useField({
- path,
+ path: pathFromContext,
} as Options)
const locale = useLocale()
diff --git a/packages/plugin-seo/src/fields/MetaTitle/index.ts b/packages/plugin-seo/src/fields/MetaTitle/index.ts
new file mode 100644
index 0000000000..fe952fc460
--- /dev/null
+++ b/packages/plugin-seo/src/fields/MetaTitle/index.ts
@@ -0,0 +1,35 @@
+import type { TextField } from 'payload'
+
+import { withMergedProps } from '@payloadcms/ui/shared'
+
+import { MetaTitleComponent } from './MetaTitleComponent.js'
+
+interface FieldFunctionProps {
+ /**
+ * Tell the component if the generate function is available as configured in the plugin config
+ */
+ hasGenerateFn?: boolean
+ overrides?: Partial
+}
+
+type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => TextField
+
+export const MetaTitleField: FieldFunction = ({ hasGenerateFn = false, overrides }) => {
+ return {
+ name: 'title',
+ type: 'text',
+ admin: {
+ components: {
+ Field: withMergedProps({
+ Component: MetaTitleComponent,
+ sanitizeServerOnlyProps: true,
+ toMergeIntoProps: {
+ hasGenerateTitleFn: hasGenerateFn,
+ },
+ }),
+ },
+ },
+ localized: true,
+ ...((overrides as unknown as TextField) ?? {}),
+ }
+}
diff --git a/packages/plugin-seo/src/ui/Overview.tsx b/packages/plugin-seo/src/fields/Overview/OverviewComponent.tsx
similarity index 69%
rename from packages/plugin-seo/src/ui/Overview.tsx
rename to packages/plugin-seo/src/fields/Overview/OverviewComponent.tsx
index e789c59222..9e79cf87f1 100644
--- a/packages/plugin-seo/src/ui/Overview.tsx
+++ b/packages/plugin-seo/src/fields/Overview/OverviewComponent.tsx
@@ -1,30 +1,44 @@
'use client'
-import type { FormField } from 'payload'
+import type { FormField, UIField } from 'payload'
import { useAllFormFields, useForm, useTranslation } from '@payloadcms/ui'
import React, { useCallback, useEffect, useState } from 'react'
-import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
+import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
-import { defaults } from '../defaults.js'
+import { defaults } from '../../defaults.js'
const {
description: { maxLength: maxDesc, minLength: minDesc },
title: { maxLength: maxTitle, minLength: minTitle },
} = defaults
-export const Overview: React.FC = () => {
+type OverviewProps = UIField & {
+ descriptionPath?: string
+ imagePath?: string
+ titlePath?: string
+}
+
+export const OverviewComponent: React.FC = ({
+ descriptionPath: descriptionPathFromContext,
+ imagePath: imagePathFromContext,
+ titlePath: titlePathFromContext,
+}) => {
const {
// dispatchFields,
getFields,
} = useForm()
+ const descriptionPath = descriptionPathFromContext || 'meta.description'
+ const titlePath = titlePathFromContext || 'meta.title'
+ const imagePath = imagePathFromContext || 'meta.image'
+
const [
{
- 'meta.description': { value: metaDesc } = {} as FormField,
- 'meta.image': { value: metaImage } = {} as FormField,
- 'meta.title': { value: metaTitle } = {} as FormField,
+ [descriptionPath]: { value: metaDesc } = {} as FormField,
+ [imagePath]: { value: metaImage } = {} as FormField,
+ [titlePath]: { value: metaTitle } = {} as FormField,
},
] = useAllFormFields()
const { t } = useTranslation()
diff --git a/packages/plugin-seo/src/fields/Overview/index.tsx b/packages/plugin-seo/src/fields/Overview/index.tsx
new file mode 100644
index 0000000000..7c980b1ace
--- /dev/null
+++ b/packages/plugin-seo/src/fields/Overview/index.tsx
@@ -0,0 +1,56 @@
+import type { UIField } from 'payload'
+
+import { withMergedProps } from '@payloadcms/ui/shared'
+
+import { OverviewComponent } from './OverviewComponent.js'
+
+interface FieldFunctionProps {
+ /**
+ * Path to the description field to use for the preview
+ *
+ * @default 'meta.description'
+ */
+ descriptionPath?: string
+ /**
+ * Path to the image field to use for the preview
+ *
+ * @default 'meta.image'
+ */
+ imagePath?: string
+ overrides?: Partial
+ /**
+ * Path to the title field to use for the preview
+ *
+ * @default 'meta.title'
+ */
+ titlePath?: string
+}
+
+type FieldFunction = ({ overrides }: FieldFunctionProps) => UIField
+
+export const OverviewField: FieldFunction = ({
+ descriptionPath,
+ imagePath,
+ overrides,
+ titlePath,
+}) => {
+ return {
+ name: 'overview',
+ type: 'ui',
+ admin: {
+ components: {
+ Field: withMergedProps({
+ Component: OverviewComponent,
+ sanitizeServerOnlyProps: true,
+ toMergeIntoProps: {
+ descriptionPath,
+ imagePath,
+ titlePath,
+ },
+ }),
+ },
+ },
+ label: 'Overview',
+ ...((overrides as unknown as UIField) ?? {}),
+ }
+}
diff --git a/packages/plugin-seo/src/ui/Preview.tsx b/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx
similarity index 80%
rename from packages/plugin-seo/src/ui/Preview.tsx
rename to packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx
index c8e7352c7e..3f081be2cf 100644
--- a/packages/plugin-seo/src/ui/Preview.tsx
+++ b/packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx
@@ -9,16 +9,23 @@ import {
useLocale,
useTranslation,
} from '@payloadcms/ui'
+import { get } from 'http'
import React, { useEffect, useState } from 'react'
-import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
-import type { GenerateURL } from '../types.js'
+import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
+import type { GenerateURL } from '../../types.js'
type PreviewProps = UIField & {
+ descriptionPath?: string
hasGenerateURLFn: boolean
+ titlePath?: string
}
-export const Preview: React.FC = ({ hasGenerateURLFn }) => {
+export const PreviewComponent: React.FC = ({
+ descriptionPath: descriptionPathFromContext,
+ hasGenerateURLFn,
+ titlePath: titlePathFromContext,
+}) => {
const { t } = useTranslation()
const locale = useLocale()
@@ -26,9 +33,12 @@ export const Preview: React.FC = ({ hasGenerateURLFn }) => {
const { getData } = useForm()
const docInfo = useDocumentInfo()
+ const descriptionPath = descriptionPathFromContext || 'meta.description'
+ const titlePath = titlePathFromContext || 'meta.title'
+
const {
- 'meta.description': { value: metaDescription } = {} as FormField,
- 'meta.title': { value: metaTitle } = {} as FormField,
+ [descriptionPath]: { value: metaDescription } = {} as FormField,
+ [titlePath]: { value: metaTitle } = {} as FormField,
} = fields
const [href, setHref] = useState()
diff --git a/packages/plugin-seo/src/fields/Preview/index.tsx b/packages/plugin-seo/src/fields/Preview/index.tsx
new file mode 100644
index 0000000000..d609c55b12
--- /dev/null
+++ b/packages/plugin-seo/src/fields/Preview/index.tsx
@@ -0,0 +1,54 @@
+import type { UIField } from 'payload'
+
+import { withMergedProps } from '@payloadcms/ui/shared'
+
+import { PreviewComponent } from './PreviewComponent.js'
+
+interface FieldFunctionProps {
+ /**
+ * Path to the description field to use for the preview
+ *
+ * @default 'meta.description'
+ */
+ descriptionPath?: string
+ /**
+ * Tell the component if the generate function is available as configured in the plugin config
+ */
+ hasGenerateFn?: boolean
+ overrides?: Partial
+ /**
+ * Path to the title field to use for the preview
+ *
+ * @default 'meta.title'
+ */
+ titlePath?: string
+}
+
+type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => UIField
+
+export const PreviewField: FieldFunction = ({
+ descriptionPath,
+ hasGenerateFn = false,
+ overrides,
+ titlePath,
+}) => {
+ return {
+ name: 'preview',
+ type: 'ui',
+ admin: {
+ components: {
+ Field: withMergedProps({
+ Component: PreviewComponent,
+ sanitizeServerOnlyProps: true,
+ toMergeIntoProps: {
+ descriptionPath,
+ hasGenerateURLFn: hasGenerateFn,
+ titlePath,
+ },
+ }),
+ },
+ },
+ label: 'Preview',
+ ...((overrides as unknown as UIField) ?? {}),
+ }
+}
diff --git a/packages/plugin-seo/src/index.tsx b/packages/plugin-seo/src/index.tsx
index 403fee6087..b030223c6a 100644
--- a/packages/plugin-seo/src/index.tsx
+++ b/packages/plugin-seo/src/index.tsx
@@ -12,12 +12,12 @@ import type {
SEOPluginConfig,
} from './types.js'
-import { MetaDescription } from './fields/MetaDescription.js'
-import { MetaImage } from './fields/MetaImage.js'
-import { MetaTitle } from './fields/MetaTitle.js'
+import { MetaDescriptionComponent } from './fields/MetaDescription/MetaDescriptionComponent.js'
+import { MetaImageComponent } from './fields/MetaImage/MetaImageComponent.js'
+import { MetaTitleComponent } from './fields/MetaTitle/MetaTitleComponent.js'
+import { OverviewComponent } from './fields/Overview/OverviewComponent.js'
+import { PreviewComponent } from './fields/Preview/PreviewComponent.js'
import { translations } from './translations/index.js'
-import { Overview } from './ui/Overview.js'
-import { Preview } from './ui/Preview.js'
export const seoPlugin =
(pluginConfig: SEOPluginConfig) =>
@@ -32,7 +32,7 @@ export const seoPlugin =
type: 'ui',
admin: {
components: {
- Field: Overview,
+ Field: OverviewComponent,
},
},
label: 'Overview',
@@ -43,7 +43,7 @@ export const seoPlugin =
admin: {
components: {
Field: withMergedProps({
- Component: MetaTitle,
+ Component: MetaTitleComponent,
sanitizeServerOnlyProps: true,
toMergeIntoProps: {
hasGenerateTitleFn: typeof pluginConfig?.generateTitle === 'function',
@@ -60,7 +60,7 @@ export const seoPlugin =
admin: {
components: {
Field: withMergedProps({
- Component: MetaDescription,
+ Component: MetaDescriptionComponent,
sanitizeServerOnlyProps: true,
toMergeIntoProps: {
hasGenerateDescriptionFn:
@@ -81,7 +81,7 @@ export const seoPlugin =
admin: {
components: {
Field: withMergedProps({
- Component: MetaImage,
+ Component: MetaImageComponent,
sanitizeServerOnlyProps: true,
toMergeIntoProps: {
hasGenerateImageFn: typeof pluginConfig?.generateImage === 'function',
@@ -105,7 +105,7 @@ export const seoPlugin =
admin: {
components: {
Field: withMergedProps({
- Component: Preview,
+ Component: PreviewComponent,
sanitizeServerOnlyProps: true,
toMergeIntoProps: {
hasGenerateURLFn: typeof pluginConfig?.generateURL === 'function',
diff --git a/test/plugin-seo/collections/PagesWithImportedFields.ts b/test/plugin-seo/collections/PagesWithImportedFields.ts
new file mode 100644
index 0000000000..7474282eec
--- /dev/null
+++ b/test/plugin-seo/collections/PagesWithImportedFields.ts
@@ -0,0 +1,98 @@
+import type { CollectionConfig } from 'payload'
+
+import {
+ MetaDescriptionField,
+ MetaImageField,
+ MetaTitleField,
+ OverviewField,
+ PreviewField,
+} from '@payloadcms/plugin-seo/fields'
+
+import { pagesWithImportedFieldsSlug } from '../shared.js'
+
+export const PagesWithImportedFields: CollectionConfig = {
+ slug: pagesWithImportedFieldsSlug,
+ labels: {
+ singular: 'Page with imported fields',
+ plural: 'Pages with imported fields',
+ },
+ admin: {
+ useAsTitle: 'title',
+ },
+ versions: {
+ drafts: true,
+ },
+ fields: [
+ {
+ name: 'title',
+ label: 'Title',
+ type: 'text',
+ required: true,
+ },
+ OverviewField({
+ titlePath: 'metaAndSEO.title',
+ descriptionPath: 'metaAndSEO.innerMeta.description',
+ imagePath: 'metaAndSEO.innerMedia.image',
+ }),
+ {
+ type: 'tabs',
+ tabs: [
+ {
+ label: 'General',
+ fields: [
+ {
+ name: 'excerpt',
+ label: 'Excerpt',
+ type: 'text',
+ },
+ {
+ name: 'slug',
+ type: 'text',
+ required: true,
+ // NOTE: in order for position: 'sidebar' to work here,
+ // the first field of this config must be of type `tabs`,
+ // and this field must be a sibling of it
+ // See `./Posts` or the `../../README.md` for more info
+ admin: {
+ position: 'sidebar',
+ },
+ },
+ ],
+ },
+ {
+ label: 'Meta',
+ name: 'metaAndSEO',
+ fields: [
+ MetaTitleField({
+ hasGenerateFn: true,
+ }),
+ PreviewField({
+ hasGenerateFn: true,
+ titlePath: 'metaAndSEO.title',
+ descriptionPath: 'metaAndSEO.innerMeta.description',
+ }),
+ {
+ type: 'group',
+ name: 'innerMeta',
+ fields: [
+ MetaDescriptionField({
+ hasGenerateFn: true,
+ }),
+ ],
+ },
+ {
+ type: 'group',
+ name: 'innerMedia',
+ fields: [
+ MetaImageField({
+ relationTo: 'media',
+ hasGenerateFn: true,
+ }),
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+}
diff --git a/test/plugin-seo/config.ts b/test/plugin-seo/config.ts
index 3dd619f727..aa978b497f 100644
--- a/test/plugin-seo/config.ts
+++ b/test/plugin-seo/config.ts
@@ -13,6 +13,7 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { Media } from './collections/Media.js'
import { Pages } from './collections/Pages.js'
+import { PagesWithImportedFields } from './collections/PagesWithImportedFields.js'
import { Users } from './collections/Users.js'
import { seed } from './seed/index.js'
@@ -29,7 +30,7 @@ const generateURL: GenerateURL = ({ doc, locale }) => {
}
export default buildConfigWithDefaults({
- collections: [Users, Pages, Media],
+ collections: [Users, Pages, Media, PagesWithImportedFields],
i18n: {
supportedLanguages: {
en,
diff --git a/test/plugin-seo/payload-types.ts b/test/plugin-seo/payload-types.ts
index 6bdb006e7a..513c1a29c6 100644
--- a/test/plugin-seo/payload-types.ts
+++ b/test/plugin-seo/payload-types.ts
@@ -11,6 +11,7 @@ export interface Config {
users: User;
pages: Page;
media: Media;
+ pagesWithImportedFields: PagesWithImportedField;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
@@ -90,6 +91,28 @@ export interface Media {
focalX?: number | null;
focalY?: number | null;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "pagesWithImportedFields".
+ */
+export interface PagesWithImportedField {
+ id: string;
+ title: string;
+ excerpt?: string | null;
+ slug: string;
+ metaAndSEO?: {
+ title?: string | null;
+ innerMeta?: {
+ description?: string | null;
+ };
+ innerMedia?: {
+ image?: string | Media | null;
+ };
+ };
+ updatedAt: string;
+ createdAt: string;
+ _status?: ('draft' | 'published') | null;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
diff --git a/test/plugin-seo/shared.ts b/test/plugin-seo/shared.ts
index fc3d1c546b..a7e31591a1 100644
--- a/test/plugin-seo/shared.ts
+++ b/test/plugin-seo/shared.ts
@@ -1,3 +1,5 @@
export const pagesSlug = 'pages'
+export const pagesWithImportedFieldsSlug = 'pagesWithImportedFields'
+
export const mediaSlug = 'media'