Browse all documentation

SEO

Add localized search metadata, draft-aware generators, upload selection, and a live result preview to collections and globals.

The SEO plugin adds metadata fields to selected collections and globals, with an overview, title and description guidance, an optional upload image, and a search-result preview.

The fields use the resource’s normal access, validation, localization, drafts, versions, hooks, REST routes, and generated types.

Use it in a new project

Generated projects include @riducms/plugin-seo in the root and admin workspaces. Add seo.New to your Go config, select the resources, and configure any generator callbacks.

Add it to an existing project

Install the package in both the root and admin workspaces:

npm install @riducms/plugin-seo
npm install --workspace admin @riducms/plugin-seo
go mod tidy

Then add seo.New below. The plugin requires resource options, so configure it in Go rather than with a zero-argument ridu plugin add entry.

Configure the paired plugin

Configure the compiled Go plugin and select the resources that receive metadata:

content/config.go
package content

import (
  "fmt"

  "github.com/riducms/ridu"
  "github.com/riducms/ridu/plugins/seo"
  "github.com/riducms/ridu/schema"
)

func Config() ridu.Config {
  return ridu.Config{
    Name: "Acme Editorial",
    Plugins: []ridu.Plugin{
      seo.New(seo.Config{
        Collections:       []schema.CollectionSlug{"pages", "posts"},
        Globals:           []schema.CollectionSlug{"site-settings"},
        UploadsCollection: "media",
        TabbedUI:          true,
        GenerateTitle: func(ctx seo.GenerateContext) (string, error) {
          title, _ := ctx.Document["title"].(string)
          return title + " | Acme", nil
        },
        GenerateURL: func(ctx seo.GenerateContext) (string, error) {
          if ctx.Collection == nil {
            return "https://example.com/", nil
          }
          slug, _ := ctx.Document["slug"].(string)
          return fmt.Sprintf("https://example.com/%s", slug), nil
        },
      }),
    },
  }
}
terminal
npm install
npm run dev

Open one configured collection or global, edit the SEO title, description, and image, save, and read the generated meta value through the SDK.

Before deployment, create and verify the immutable migration:

terminal
ridu migrate create --name add-seo
ridu migrate verify
ridu check

Apply the reviewed artifact with migrate up during deployment, then require a clean migrate status. Use the selected adapter’s connection flags and safety admission for those commands.

Default field shape

The plugin injects one meta group in this order:

Path Field behavior
meta.overview Presentation-only summary of title, description, and image checks.
meta.title Text with 50–60 character guidance and optional generation.
meta.description Textarea with 100–150 character guidance and optional generation.
meta.image Optional upload reference when UploadsCollection is configured.
meta.preview Presentation-only result preview using the current unsaved title and description.

The character ranges are authoring guidance, not validation. Add .MinLength(...) or .MaxLength(...) through a field override when the server must reject values outside a range.

Injected metadata fields become localized when application content localization is enabled. The admin shows exact-language values, reports missing fallback values, and passes the selected locale to generation callbacks.

UploadsCollection must name an upload-enabled collection. Authors can create an upload through the host authoring drawer, choose an existing asset, inspect and edit the selected upload, remove the image from this document, or ask the configured image generator for an asset ID. Upload access and normal document validation still apply.

Write and review metadata

Open a configured document’s SEO fields. The overview counts title, description, and image checks; the suggested title and description lengths guide the author but do not block Save. Enter a title and description, or choose Auto-generate beside a field when its callback is configured. The description input grows as you type. The result preview reads the current unsaved title and description and uses GenerateURL when configured.

For the image, choose Create new to upload an asset with its own metadata, or Choose existing to select one from the configured upload collection. When creation is allowed, dropping an image on the empty image area also opens the asset drawer. A selected image shows its filename, dimensions, size, and MIME type. Inspect image opens the asset for permitted edits, including replacing its file; Remove image clears only this document’s reference. Save the parent document to persist the chosen image ID.

Place metadata in tabs

Set TabbedUI: true to place metadata in a separate tab. Ridu appends an SEO tab to an existing leading tab group. If fields are not already tabbed, it builds one tab group: ordinary content under the resource label, then any authored tabs, then SEO. An auth collection’s email field stays outside the tabs so authentication semantics do not change.

Without TabbedUI, the meta group is appended to the selected resource’s existing fields.

Generate from the unsaved draft

Each callback receives a GenerateContext containing:

  • the current unsaved document snapshot;
  • the document ID, when editing an existing document;
  • the selected content locale;
  • a detached collection or global definition; and
  • the authenticated plugin endpoint context, including the access-controlled local API.

GenerateTitle, GenerateDescription, and GenerateImage require create access for a new collection document, update access for an existing document, or update access for a global. GenerateURL requires create access for a new document and read access for an existing collection document or global. Every generation call requires an authenticated actor.

Generated values are proposed form edits. The author can inspect or change them, and normal save or publish validation persists them. A generator never writes around the form or operation engine.

The admin cancels superseded calls and discards a response when the draft, resource, ID, or locale has changed. While a URL request is pending or has failed, the preview clears the old URL instead of presenting it as current.

Replace or directly place fields

Config.Fields receives a copy of the ordered defaults and returns the complete contents of the injected meta group. Use it to add validation, change labels, insert fields, or remove a default:

go
Fields: func(defaults field.Fields) (field.Fields, error) {
  return defaults.Edit(func(fields *field.ChildrenDraft) error {
    return fields.EditText("title", func(
      title field.TextField,
    ) field.TextField {
      return title.Required().MaxLength(70)
    })
  })
},

The callback must return at least one field. Its output is resolved and validated like application config, so duplicate names, incompatible options, invalid renderer metadata, and bad upload targets fail before startup.

Overview, MetaTitle, MetaDescription, MetaImage, and Preview are also public constructors for direct placement. Their WithConfig variants map overview and preview components to custom paths or customize image presentation. Direct title, description, and image constructors are localized by default; use injected fields or an ordinary field plus the lower-level admin-component contract when an application has no localization configuration.

Collections and Globals select automatic injection. Direct fields can live on another configured resource: the authenticated generation endpoint resolves that resource and applies its ordinary access rules even when it is not in an injection selector.

Endpoint and failure behavior

The plugin declares four POST endpoints under /api/plugins/seo/:

Endpoint Result
generate-title { "result": "..." } from GenerateTitle.
generate-description { "result": "..." } from GenerateDescription.
generate-image An upload document ID from GenerateImage.
generate-url A preview URL from GenerateURL.

All four routes remain present when a callback is omitted and return an empty result. Requests are limited to 1 MiB, reject unknown or trailing JSON, require exactly one collection or global, and return the normal structured Ridu error envelope. Use the SDK’s requestPlugin method rather than building a URL manually; it retains base URL, credentials, headers, middleware, cancellation, and RiduError behavior.

Authoring state Result
Callback is not configured Auto-generation is not offered; the endpoint returns an empty result.
Actor lacks resource access The endpoint returns access_denied; existing field values remain intact.
Callback fails The admin reports a retryable generation error and does not change value.
Draft changes during generation The old request is cancelled or its response is discarded.
Referenced image is unreadable/deleted The field reports the unavailable reference without inventing metadata.

Scope and boundaries

The plugin owns search metadata authoring. It does not inject canonical links, robots directives, Open Graph variants, Twitter cards, JSON-LD, redirects, sitemaps, or frontend <head> rendering. Read the stored metadata in your application frontend and render the exact tags your delivery surface needs.

See the seo Go reference, @riducms/plugin-seo reference, Plugin system, Localization, Uploads, and TypeScript SDK.