Skip to content

Commit 25d368a

Browse files
authored
feat(plugin-seo): export fields from plugin seo so that they can be imported freely in a collection fields config (#6996)
Exports the fields from the SEO plugin so that they can be used anywhere inside a collection, new exports: ```ts import { MetaDescriptionField, MetaImageField, MetaTitleField, OverviewField, PreviewField } from '@payloadcms/plugin-seo/fields' // Used as fields MetaImageField({ relationTo: 'media', hasGenerateFn: true, }) MetaDescriptionField({ hasGenerateFn: true, }) MetaTitleField({ hasGenerateFn: true, }) PreviewField({ hasGenerateFn: true, titlePath: 'meta.title', descriptionPath: 'meta.description', }) OverviewField({ titlePath: 'meta.title', descriptionPath: 'meta.description', imagePath: 'meta.image', }) ```
1 parent 0711f88 commit 25d368a

File tree

18 files changed

+492
-48
lines changed

18 files changed

+492
-48
lines changed

docs/plugins/seo.mdx

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const config = buildConfig({
6868
'pages',
6969
],
7070
uploadsCollection: 'media',
71-
generateTitle: ({ doc }) => `Website.com — ${doc.title.value}`,
71+
generateTitle: ({ doc }) => `Website.com — ${doc.title}`,
7272
generateDescription: ({ doc }) => doc.excerpt
7373
})
7474
]
@@ -119,7 +119,7 @@ A function that allows you to return any meta title, including from document's c
119119
{
120120
// ...
121121
seoPlugin({
122-
generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title?.value}`,
122+
generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title}`,
123123
})
124124
}
125125
```
@@ -133,7 +133,7 @@ A function that allows you to return any meta description, including from docume
133133
{
134134
// ...
135135
seoPlugin({
136-
generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt?.value,
136+
generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt,
137137
})
138138
}
139139
```
@@ -147,7 +147,7 @@ A function that allows you to return any meta image, including from document's c
147147
{
148148
// ...
149149
seoPlugin({
150-
generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage?.value,
150+
generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage,
151151
})
152152
}
153153
```
@@ -162,7 +162,7 @@ A function called by the search preview component to display the actual URL of y
162162
// ...
163163
seoPlugin({
164164
generateURL: ({ ...docInfo, doc, locale }) =>
165-
`https://yoursite.com/${collection?.slug}/${doc?.slug?.value}`,
165+
`https://yoursite.com/${collection?.slug}/${doc?.slug}`,
166166
})
167167
}
168168
```
@@ -200,6 +200,54 @@ seoPlugin({
200200
})
201201
```
202202

203+
## Direct use of fields
204+
205+
There is the option to directly import any of the fields from the plugin so that you can include them anywhere as needed.
206+
207+
<Banner type="info">
208+
You will still need to configure the plugin in the Payload config in order to configure the generation functions.
209+
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.
210+
</Banner>
211+
212+
```ts
213+
import { MetaDescriptionField, MetaImageField, MetaTitleField, OverviewField, PreviewField } from '@payloadcms/plugin-seo/fields'
214+
215+
// Used as fields
216+
MetaImageField({
217+
// the upload collection slug
218+
relationTo: 'media',
219+
220+
// if the `generateImage` function is configured
221+
hasGenerateFn: true,
222+
})
223+
224+
MetaDescriptionField({
225+
// if the `generateDescription` function is configured
226+
hasGenerateFn: true,
227+
})
228+
229+
MetaTitleField({
230+
// if the `generateTitle` function is configured
231+
hasGenerateFn: true,
232+
})
233+
234+
PreviewField({
235+
// if the `generateUrl` function is configured
236+
hasGenerateFn: true,
237+
238+
// field paths to match the target field for data
239+
titlePath: 'meta.title',
240+
descriptionPath: 'meta.description',
241+
})
242+
243+
OverviewField({
244+
// field paths to match the target field for data
245+
titlePath: 'meta.title',
246+
descriptionPath: 'meta.description',
247+
imagePath: 'meta.image',
248+
})
249+
```
250+
203251
## TypeScript
204252

205253
All types can be directly imported:
@@ -213,6 +261,18 @@ import {
213261
} from '@payloadcms/plugin-seo/types';
214262
```
215263

264+
You can then pass the collections from your generated Payload types into the generation types, for example:
265+
266+
```ts
267+
import { Page } from './payload-types.ts';
268+
269+
import { GenerateTitle } from '@payloadcms/plugin-seo/types';
270+
271+
const generateTitle: GenerateTitle<Page> = async ({ doc, locale }) => {
272+
return `Website.com — ${doc?.title}`
273+
}
274+
```
275+
216276
## Examples
217277

218278
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.

packages/plugin-seo/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
"import": "./src/exports/types.ts",
3030
"types": "./src/exports/types.ts",
3131
"default": "./src/exports/types.ts"
32+
},
33+
"./fields": {
34+
"import": "./src/exports/fields.ts",
35+
"types": "./src/exports/fields.ts",
36+
"default": "./src/exports/fields.ts"
3237
}
3338
},
3439
"main": "./src/index.tsx",
@@ -73,6 +78,11 @@
7378
"import": "./dist/exports/types.js",
7479
"types": "./dist/exports/types.d.ts",
7580
"default": "./dist/exports/types.js"
81+
},
82+
"./fields": {
83+
"import": "./dist/exports/fields.js",
84+
"types": "./dist/exports/fields.d.ts",
85+
"default": "./dist/exports/fields.js"
7686
}
7787
},
7888
"main": "./dist/index.js",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { MetaDescriptionField } from '../fields/MetaDescription/index.js'
2+
export { MetaImageField } from '../fields/MetaImage/index.js'
3+
export { MetaTitleField } from '../fields/MetaTitle/index.js'
4+
export { OverviewField } from '../fields/Overview/index.js'
5+
export { PreviewField } from '../fields/Preview/index.js'

packages/plugin-seo/src/fields/MetaDescription.tsx renamed to packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import {
1414
} from '@payloadcms/ui'
1515
import React, { useCallback } from 'react'
1616

17-
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
18-
import type { GenerateDescription } from '../types.js'
17+
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
18+
import type { GenerateDescription } from '../../types.js'
1919

20-
import { defaults } from '../defaults.js'
21-
import { LengthIndicator } from '../ui/LengthIndicator.js'
20+
import { defaults } from '../../defaults.js'
21+
import { LengthIndicator } from '../../ui/LengthIndicator.js'
2222

2323
const { maxLength, minLength } = defaults.description
2424

@@ -28,8 +28,8 @@ type MetaDescriptionProps = FormFieldBase & {
2828
path: string
2929
}
3030

31-
export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
32-
const { CustomLabel, hasGenerateDescriptionFn, label, labelProps, path, required } = props
31+
export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props) => {
32+
const { CustomLabel, hasGenerateDescriptionFn, label, labelProps, required } = props
3333
const { path: pathFromContext } = useFieldProps()
3434

3535
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
@@ -39,7 +39,7 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
3939
const docInfo = useDocumentInfo()
4040

4141
const field: FieldType<string> = useField({
42-
path,
42+
path: pathFromContext,
4343
} as Options)
4444

4545
const { errorMessage, setValue, showError, value } = field
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { TextareaField } from 'payload'
2+
3+
import { withMergedProps } from '@payloadcms/ui/shared'
4+
5+
import { MetaDescriptionComponent } from './MetaDescriptionComponent.js'
6+
7+
interface FieldFunctionProps {
8+
/**
9+
* Tell the component if the generate function is available as configured in the plugin config
10+
*/
11+
hasGenerateFn?: boolean
12+
overrides?: Partial<TextareaField>
13+
}
14+
15+
type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => TextareaField
16+
17+
export const MetaDescriptionField: FieldFunction = ({ hasGenerateFn = false, overrides }) => {
18+
return {
19+
name: 'description',
20+
type: 'textarea',
21+
admin: {
22+
components: {
23+
Field: withMergedProps({
24+
Component: MetaDescriptionComponent,
25+
sanitizeServerOnlyProps: true,
26+
toMergeIntoProps: {
27+
hasGenerateDescriptionFn: hasGenerateFn,
28+
},
29+
}),
30+
},
31+
},
32+
localized: true,
33+
...((overrides as unknown as TextareaField) ?? {}),
34+
}
35+
}

packages/plugin-seo/src/fields/MetaImage.tsx renamed to packages/plugin-seo/src/fields/MetaImage/MetaImageComponent.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,28 @@ import {
88
useConfig,
99
useDocumentInfo,
1010
useField,
11+
useFieldProps,
1112
useForm,
1213
useLocale,
1314
useTranslation,
1415
} from '@payloadcms/ui'
1516
import React, { useCallback } from 'react'
1617

17-
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
18-
import type { GenerateImage } from '../types.js'
18+
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
19+
import type { GenerateImage } from '../../types.js'
1920

20-
import { Pill } from '../ui/Pill.js'
21+
import { Pill } from '../../ui/Pill.js'
2122

2223
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
2324
type MetaImageProps = UploadInputProps & {
2425
hasGenerateImageFn: boolean
2526
}
2627

27-
export const MetaImage: React.FC<MetaImageProps> = (props) => {
28+
export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
2829
const { CustomLabel, hasGenerateImageFn, label, labelProps, relationTo, required } = props || {}
30+
const { path: pathFromContext } = useFieldProps()
2931

30-
const field: FieldType<string> = useField(props as Options)
32+
const field: FieldType<string> = useField({ ...props, path: pathFromContext } as Options)
3133

3234
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
3335

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { UploadField } from 'payload'
2+
3+
import { withMergedProps } from '@payloadcms/ui/shared'
4+
5+
import { MetaImageComponent } from './MetaImageComponent.js'
6+
7+
interface FieldFunctionProps {
8+
/**
9+
* Tell the component if the generate function is available as configured in the plugin config
10+
*/
11+
hasGenerateFn?: boolean
12+
overrides?: Partial<UploadField>
13+
relationTo: string
14+
}
15+
16+
type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => UploadField
17+
18+
export const MetaImageField: FieldFunction = ({ hasGenerateFn = false, overrides, relationTo }) => {
19+
return {
20+
name: 'image',
21+
type: 'upload',
22+
admin: {
23+
components: {
24+
Field: withMergedProps({
25+
Component: MetaImageComponent,
26+
sanitizeServerOnlyProps: true,
27+
toMergeIntoProps: {
28+
hasGenerateImageFn: hasGenerateFn,
29+
},
30+
}),
31+
},
32+
description: 'Maximum upload file size: 12MB. Recommended file size for images is <500KB.',
33+
},
34+
label: 'Meta Image',
35+
localized: true,
36+
relationTo,
37+
...((overrides as unknown as UploadField) ?? {}),
38+
}
39+
}

packages/plugin-seo/src/fields/MetaTitle.tsx renamed to packages/plugin-seo/src/fields/MetaTitle/MetaTitleComponent.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import {
1414
} from '@payloadcms/ui'
1515
import React, { useCallback } from 'react'
1616

17-
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../translations/index.js'
18-
import type { GenerateTitle } from '../types.js'
17+
import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../translations/index.js'
18+
import type { GenerateTitle } from '../../types.js'
1919

20-
import { defaults } from '../defaults.js'
21-
import { LengthIndicator } from '../ui/LengthIndicator.js'
22-
import './index.scss'
20+
import { defaults } from '../../defaults.js'
21+
import { LengthIndicator } from '../../ui/LengthIndicator.js'
22+
import '../index.scss'
2323

2424
const { maxLength, minLength } = defaults.title
2525

@@ -28,14 +28,14 @@ type MetaTitleProps = FormFieldBase & {
2828
hasGenerateTitleFn: boolean
2929
}
3030

31-
export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
32-
const { CustomLabel, hasGenerateTitleFn, label, labelProps, path, required } = props || {}
31+
export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
32+
const { CustomLabel, hasGenerateTitleFn, label, labelProps, required } = props || {}
3333
const { path: pathFromContext } = useFieldProps()
3434

3535
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
3636

3737
const field: FieldType<string> = useField({
38-
path,
38+
path: pathFromContext,
3939
} as Options)
4040

4141
const locale = useLocale()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { TextField } from 'payload'
2+
3+
import { withMergedProps } from '@payloadcms/ui/shared'
4+
5+
import { MetaTitleComponent } from './MetaTitleComponent.js'
6+
7+
interface FieldFunctionProps {
8+
/**
9+
* Tell the component if the generate function is available as configured in the plugin config
10+
*/
11+
hasGenerateFn?: boolean
12+
overrides?: Partial<TextField>
13+
}
14+
15+
type FieldFunction = ({ hasGenerateFn, overrides }: FieldFunctionProps) => TextField
16+
17+
export const MetaTitleField: FieldFunction = ({ hasGenerateFn = false, overrides }) => {
18+
return {
19+
name: 'title',
20+
type: 'text',
21+
admin: {
22+
components: {
23+
Field: withMergedProps({
24+
Component: MetaTitleComponent,
25+
sanitizeServerOnlyProps: true,
26+
toMergeIntoProps: {
27+
hasGenerateTitleFn: hasGenerateFn,
28+
},
29+
}),
30+
},
31+
},
32+
localized: true,
33+
...((overrides as unknown as TextField) ?? {}),
34+
}
35+
}

0 commit comments

Comments
 (0)