Skip to content

SEO tab

@bison-lab/payload-core wraps @payloadcms/plugin-seo so every Bison Lab site gets the same tab without copying the configuration. Every string a site would spell is an option. The plugin never reads process.env and never assumes a route.

An SEO tab beside the page’s Content tab, never below the block editor: an overview with character counts, meta title and description with Generate buttons, a share image from the media collection with its own Generate button when the site can name an image already on the page, a search-result preview, and a Hide this page from search engines switch, off by default.

  1. Configure the plugin. The site spells its name and its URL scheme once.

    payload.config.ts
    import { firstImageIn, seoPlugin } from "@bison-lab/payload-core";
    import type { Page } from "@/payload-types";
    export default buildConfig({
    plugins: [
    seoPlugin<Page>({
    siteName: "Acme Clinics",
    urlFor: (doc) => (doc.slug ? absoluteUrl(pagePath(doc.slug)) : undefined),
    describeFrom: (doc) => doc.hero?.[0]?.body,
    imageFor: (doc) => firstImageIn([...(doc.hero ?? []), ...(doc.layout ?? [])]),
    }),
    ],
    });

    collections defaults to ["pages"] and uploadsCollection to "media". urlFor answering undefined leaves the preview empty rather than pointing it at the home page while the slug is still blank. describeFrom and imageFor are optional; without them those Generate buttons fill in nothing, and the image button does not appear at all.

  2. Cut the share rendition. Add SHARE_IMAGE_SIZE to the upload collection so every image gets a 1200 by 630 card:

    import { SHARE_IMAGE_SIZE } from "@bison-lab/payload-core";
    export const Media: CollectionConfig = {
    slug: "media",
    upload: { imageSizes: [SHARE_IMAGE_SIZE] },
    fields: [{ name: "alt", type: "text", required: true }],
    };
  3. Run the three commands. payload generate:types, payload generate:importmap (the plugin’s admin components resolve by string key from the site’s import map) and payload migrate:create.

Then read the tab back with pageMetadata.

noIndexField and the plugin’s own fields are columns in every consuming site. A change to them is a schema change: the changeset says “run payload migrate:create”.