Skip to content

Commit aee76cb

Browse files
authored
fix(plugin-seo): threads entity slug and document config through generation fn args (#8238)
The `generateTitle`, `generateDescription`, `generateURL`, and `generateImage` functions in the SEO Plugin do not currently receive any args representing the document's entity. This means that within these functions, it is currently not possible to discern the _type_ of document you are working with, i.e. a collection or global. The underlying problem here was that the request made to execute these functions was threading through `slug` as `undefined`. This is because the `DocumentInfoProvider` was failing to thread this prop through context as the types suggest. Now, these functions receive their respective `collectionConfig` and `globalConfig`. ```ts import type { GenerateTitle } from '@payloadcms/plugin-seo/types' import type { Page } from '@/payload-types' const generateTitle: GenerateTitle<Page> = ({ doc, collectionConfig, globalConfig, }) => { return `Website.com — ${doc?.title}` } ```
1 parent b7db53c commit aee76cb

File tree

8 files changed

+130
-30
lines changed

8 files changed

+130
-30
lines changed

docs/plugins/seo.mdx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,33 @@ A function that allows you to return any meta title, including from document's c
119119
{
120120
// ...
121121
seoPlugin({
122-
generateTitle: ({ ...docInfo, doc, locale, req }) => `Website.com — ${doc?.title}`,
122+
generateTitle: ({ doc }) => `Website.com — ${doc?.title}`,
123123
})
124124
}
125125
```
126126

127+
All "generate" functions receive the following arguments:
128+
129+
| Argument | Description |
130+
| --- | --- |
131+
| **`collectionConfig`** | The configuration of the collection. |
132+
| **`collectionSlug`** | The slug of the collection. |
133+
| **`doc`** | The data of the current document. |
134+
| **`docPermissions`** | The permissions of the document. |
135+
| **`globalConfig`** | The configuration of the global. |
136+
| **`globalSlug`** | The slug of the global. |
137+
| **`hasPublishPermission`** | Whether the user has permission to publish the document. |
138+
| **`hasSavePermission`** | Whether the user has permission to save the document. |
139+
| **`id`** | The ID of the document. |
140+
| **`initialData`** | The initial data of the document. |
141+
| **`initialState`** | The initial state of the document. |
142+
| **`locale`** | The locale of the document. |
143+
| **`preferencesKey`** | The preferences key of the document. |
144+
| **`publishedDoc`** | The published document. |
145+
| **`req`** | The Payload request object containing `user`, `payload`, `i18n`, etc. |
146+
| **`title`** | The title of the document. |
147+
| **`versionsCount`** | The number of versions of the document. |
148+
127149
##### `generateDescription`
128150

129151
A function that allows you to return any meta description, including from document's content.
@@ -133,11 +155,13 @@ A function that allows you to return any meta description, including from docume
133155
{
134156
// ...
135157
seoPlugin({
136-
generateDescription: ({ ...docInfo, doc, locale, req }) => doc?.excerpt,
158+
generateDescription: ({ doc }) => doc?.excerpt,
137159
})
138160
}
139161
```
140162

163+
For a full list of arguments, see the [`generateTitle`](#generateTitle) function.
164+
141165
##### `generateImage`
142166

143167
A function that allows you to return any meta image, including from document's content.
@@ -147,11 +171,13 @@ A function that allows you to return any meta image, including from document's c
147171
{
148172
// ...
149173
seoPlugin({
150-
generateImage: ({ ...docInfo, doc, locale, req }) => doc?.featuredImage,
174+
generateImage: ({ doc }) => doc?.featuredImage,
151175
})
152176
}
153177
```
154178

179+
For a full list of arguments, see the [`generateTitle`](#generateTitle) function.
180+
155181
##### `generateURL`
156182

157183
A function called by the search preview component to display the actual URL of your page.
@@ -161,12 +187,14 @@ A function called by the search preview component to display the actual URL of y
161187
{
162188
// ...
163189
seoPlugin({
164-
generateURL: ({ ...docInfo, doc, locale, req }) =>
165-
`https://yoursite.com/${collection?.slug}/${doc?.slug}`,
190+
generateURL: ({ doc, collectionSlug }) =>
191+
`https://yoursite.com/${collectionSlug}/${doc?.slug}`,
166192
})
167193
}
168194
```
169195

196+
For a full list of arguments, see the [`generateTitle`](#generateTitle) function.
197+
170198
#### `interfaceName`
171199

172200
Rename the meta group interface name that is generated for TypeScript and GraphQL.

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,20 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
6161
const genDescriptionResponse = await fetch('/api/plugin-seo/generate-description', {
6262
body: JSON.stringify({
6363
id: docInfo.id,
64-
slug: docInfo.slug,
64+
collectionSlug: docInfo.collectionSlug,
6565
doc: getData(),
6666
docPermissions: docInfo.docPermissions,
67+
globalSlug: docInfo.globalSlug,
6768
hasPublishPermission: docInfo.hasPublishPermission,
6869
hasSavePermission: docInfo.hasSavePermission,
6970
initialData: docInfo.initialData,
7071
initialState: docInfo.initialState,
7172
locale: typeof locale === 'object' ? locale?.code : locale,
7273
title: docInfo.title,
73-
} satisfies Omit<Parameters<GenerateDescription>[0], 'req'>),
74+
} satisfies Omit<
75+
Parameters<GenerateDescription>[0],
76+
'collectionConfig' | 'globalConfig' | 'req'
77+
>),
7478
credentials: 'include',
7579
headers: {
7680
'Content-Type': 'application/json',

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,17 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
5858
const genImageResponse = await fetch('/api/plugin-seo/generate-image', {
5959
body: JSON.stringify({
6060
id: docInfo.id,
61-
slug: docInfo.slug,
61+
collectionSlug: docInfo.collectionSlug,
6262
doc: getData(),
6363
docPermissions: docInfo.docPermissions,
64+
globalSlug: docInfo.globalSlug,
6465
hasPublishPermission: docInfo.hasPublishPermission,
6566
hasSavePermission: docInfo.hasSavePermission,
6667
initialData: docInfo.initialData,
6768
initialState: docInfo.initialState,
6869
locale: typeof locale === 'object' ? locale?.code : locale,
6970
title: docInfo.title,
70-
} satisfies Omit<Parameters<GenerateImage>[0], 'req'>),
71+
} satisfies Omit<Parameters<GenerateImage>[0], 'collectionConfig' | 'globalConfig' | 'req'>),
7172
credentials: 'include',
7273
headers: {
7374
'Content-Type': 'application/json',

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,17 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
6262
const genTitleResponse = await fetch('/api/plugin-seo/generate-title', {
6363
body: JSON.stringify({
6464
id: docInfo.id,
65-
slug: docInfo.slug,
65+
collectionSlug: docInfo.collectionSlug,
6666
doc: getData(),
6767
docPermissions: docInfo.docPermissions,
68+
globalSlug: docInfo.globalSlug,
6869
hasPublishPermission: docInfo.hasPublishPermission,
6970
hasSavePermission: docInfo.hasSavePermission,
7071
initialData: docInfo.initialData,
7172
initialState: docInfo.initialState,
7273
locale: typeof locale === 'object' ? locale?.code : locale,
7374
title: docInfo.title,
74-
} satisfies Omit<Parameters<GenerateTitle>[0], 'req'>),
75+
} satisfies Omit<Parameters<GenerateTitle>[0], 'collectionConfig' | 'globalConfig' | 'req'>),
7576
credentials: 'include',
7677
headers: {
7778
'Content-Type': 'application/json',

packages/plugin-seo/src/fields/Preview/PreviewComponent.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,17 @@ export const PreviewComponent: React.FC<PreviewProps> = (props) => {
4949
const genURLResponse = await fetch('/api/plugin-seo/generate-url', {
5050
body: JSON.stringify({
5151
id: docInfo.id,
52+
collectionSlug: docInfo.collectionSlug,
5253
doc: getData(),
5354
docPermissions: docInfo.docPermissions,
55+
globalSlug: docInfo.globalSlug,
5456
hasPublishPermission: docInfo.hasPublishPermission,
5557
hasSavePermission: docInfo.hasSavePermission,
5658
initialData: docInfo.initialData,
5759
initialState: docInfo.initialState,
5860
locale: typeof locale === 'object' ? locale?.code : locale,
5961
title: docInfo.title,
60-
} satisfies Omit<Parameters<GenerateURL>[0], 'req'>),
62+
} satisfies Omit<Parameters<GenerateURL>[0], 'collectionConfig' | 'globalConfig' | 'req'>),
6163
credentials: 'include',
6264
headers: {
6365
'Content-Type': 'application/json',

packages/plugin-seo/src/index.tsx

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,26 @@ export const seoPlugin =
132132
...(config.endpoints ?? []),
133133
{
134134
handler: async (req) => {
135-
const data = await req.json()
135+
const data: Omit<
136+
Parameters<GenerateTitle>[0],
137+
'collectionConfig' | 'globalConfig' | 'req'
138+
> = await req.json()
136139

137140
if (data) {
138141
req.data = data
139142
}
140143

141144
const result = pluginConfig.generateTitle
142145
? await pluginConfig.generateTitle({
143-
...req.data,
146+
...data,
147+
collectionConfig: req.data.collectionSlug
148+
? config.collections?.find((c) => c.slug === req.data.collectionSlug)
149+
: null,
150+
globalConfig: req.data.globalSlug
151+
? config.globals?.find((g) => g.slug === req.data.globalSlug)
152+
: null,
144153
req,
145-
} as unknown as Parameters<GenerateTitle>[0])
154+
} satisfies Parameters<GenerateTitle>[0])
146155
: ''
147156
return new Response(JSON.stringify({ result }), { status: 200 })
148157
},
@@ -151,17 +160,26 @@ export const seoPlugin =
151160
},
152161
{
153162
handler: async (req) => {
154-
const data = await req.json()
163+
const data: Omit<
164+
Parameters<GenerateTitle>[0],
165+
'collectionConfig' | 'globalConfig' | 'req'
166+
> = await req.json()
155167

156168
if (data) {
157169
req.data = data
158170
}
159171

160172
const result = pluginConfig.generateDescription
161173
? await pluginConfig.generateDescription({
162-
...req.data,
174+
...data,
175+
collectionConfig: req.data.collectionSlug
176+
? config.collections?.find((c) => c.slug === req.data.collectionSlug)
177+
: null,
178+
globalConfig: req.data.globalSlug
179+
? config.globals?.find((g) => g.slug === req.data.globalSlug)
180+
: null,
163181
req,
164-
} as unknown as Parameters<GenerateDescription>[0])
182+
} satisfies Parameters<GenerateDescription>[0])
165183
: ''
166184
return new Response(JSON.stringify({ result }), { status: 200 })
167185
},
@@ -170,17 +188,26 @@ export const seoPlugin =
170188
},
171189
{
172190
handler: async (req) => {
173-
const data = await req.json()
191+
const data: Omit<
192+
Parameters<GenerateTitle>[0],
193+
'collectionConfig' | 'globalConfig' | 'req'
194+
> = await req.json()
174195

175196
if (data) {
176197
req.data = data
177198
}
178199

179200
const result = pluginConfig.generateURL
180201
? await pluginConfig.generateURL({
181-
...req.data,
202+
...data,
203+
collectionConfig: req.data.collectionSlug
204+
? config.collections?.find((c) => c.slug === req.data.collectionSlug)
205+
: null,
206+
globalConfig: req.data.globalSlug
207+
? config.globals?.find((g) => g.slug === req.data.globalSlug)
208+
: null,
182209
req,
183-
} as unknown as Parameters<GenerateURL>[0])
210+
} satisfies Parameters<GenerateURL>[0])
184211
: ''
185212
return new Response(JSON.stringify({ result }), { status: 200 })
186213
},
@@ -189,17 +216,26 @@ export const seoPlugin =
189216
},
190217
{
191218
handler: async (req) => {
192-
const data = await req.json()
219+
const data: Omit<
220+
Parameters<GenerateTitle>[0],
221+
'collectionConfig' | 'globalConfig' | 'req'
222+
> = await req.json()
193223

194224
if (data) {
195225
req.data = data
196226
}
197227

198228
const result = pluginConfig.generateImage
199229
? await pluginConfig.generateImage({
200-
...req.data,
230+
...data,
231+
collectionConfig: req.data.collectionSlug
232+
? config.collections?.find((c) => c.slug === req.data.collectionSlug)
233+
: null,
234+
globalConfig: req.data.globalSlug
235+
? config.globals?.find((g) => g.slug === req.data.globalSlug)
236+
: null,
201237
req,
202-
} as unknown as Parameters<GenerateImage>[0])
238+
} as Parameters<GenerateImage>[0])
203239
: ''
204240
return new Response(result, { status: 200 })
205241
},

packages/plugin-seo/src/types.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,68 @@
11
import type { DocumentInfoContext } from '@payloadcms/ui'
2-
import type { Field, PayloadRequest, TextareaField, TextField, UploadField } from 'payload'
2+
import type {
3+
CollectionConfig,
4+
Field,
5+
GlobalConfig,
6+
PayloadRequest,
7+
TextareaField,
8+
TextField,
9+
UploadField,
10+
} from 'payload'
311

412
export type PartialDocumentInfoContext = Pick<
513
DocumentInfoContext,
14+
| 'collectionSlug'
615
| 'docPermissions'
16+
| 'globalSlug'
717
| 'hasPublishPermission'
818
| 'hasSavePermission'
919
| 'id'
1020
| 'initialData'
1121
| 'initialState'
1222
| 'preferencesKey'
1323
| 'publishedDoc'
14-
| 'slug'
1524
| 'title'
1625
| 'versionsCount'
1726
>
1827

1928
export type GenerateTitle<T = any> = (
20-
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
29+
args: {
30+
collectionConfig?: CollectionConfig
31+
doc: T
32+
globalConfig?: GlobalConfig
33+
locale?: string
34+
req: PayloadRequest
35+
} & PartialDocumentInfoContext,
2136
) => Promise<string> | string
2237

2338
export type GenerateDescription<T = any> = (
2439
args: {
40+
collectionConfig?: CollectionConfig
2541
doc: T
42+
globalConfig?: GlobalConfig
2643
locale?: string
2744
req: PayloadRequest
2845
} & PartialDocumentInfoContext,
2946
) => Promise<string> | string
3047

3148
export type GenerateImage<T = any> = (
32-
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
49+
args: {
50+
collectionConfig?: CollectionConfig
51+
doc: T
52+
globalConfig?: GlobalConfig
53+
locale?: string
54+
req: PayloadRequest
55+
} & PartialDocumentInfoContext,
3356
) => Promise<string> | string
3457

3558
export type GenerateURL<T = any> = (
36-
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
59+
args: {
60+
collectionConfig?: CollectionConfig
61+
doc: T
62+
globalConfig?: GlobalConfig
63+
locale?: string
64+
req: PayloadRequest
65+
} & PartialDocumentInfoContext,
3766
) => Promise<string> | string
3867

3968
export type SEOPluginConfig = {

packages/ui/src/providers/DocumentInfo/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export type DocumentInfoContext = {
7171
fieldPreferences: { [key: string]: unknown } & Partial<InsideFieldsPreferences>,
7272
) => void
7373
setDocumentTitle: (title: string) => void
74-
slug?: string
7574
title: string
7675
unpublishedVersions?: PaginatedDocs<TypeWithVersion<any>>
7776
versions?: PaginatedDocs<TypeWithVersion<any>>

0 commit comments

Comments
 (0)