• Official Extension
SEO Assistant

README

ApostropheCMS logo

SEO Assistant

Generate compelling, keyword-optimized meta titles and descriptions with AI. Transform your SEO workflow by automatically creating professional metadata that drives clicks and improves search rankings—all based on your actual page content.

Why SEO Assistant?

  • 🤖 AI-Generated Meta Titles: Compelling, keyword-optimized titles generated automatically
  • 📝 Smart Meta Descriptions: AI-crafted descriptions that drive clicks
  • 🎯 Content Analysis: Get suggestions based on your actual page content
  • ⚡ One-Click Optimization: Generate, review, and apply SEO improvements instantly
  • 🔄 Multiple Suggestions: Try different approaches with regeneration options
  • ✏️ Custom Prompts: Fine-tune AI behavior for your brand voice

The SEO Assistant analyzes your page content and generates optimized meta titles and descriptions using advanced AI, making professional SEO accessible to content creators of all skill levels.

Installation

Note: This module requires an ApostropheCMS Pro license. Don't have Pro yet? Create an account on Apostrophe Workspaces or contact us and get started with ApostropheCMS Pro to access this and other Pro extensions.

To install the module, use the command line to run this command in an ApostropheCMS project's root directory:

bash
npm install @apostrophecms-pro/seo-assistant @apostrophecms/seo

The SEO extension is also required, if not already present, for the SEO Assistant module to work.

Requirements: the assistant generates through the Apostrophe AI engine (apos.ai) and builds its page context with the core schema content extraction (apos.schema.extract) - update apostrophe to the latest version. The @apostrophecms-pro/automatic-translation extension is no longer required.

Upgrading from 1.x? The module no longer carries its own OpenAI client, and its AI configuration moved to the engine. See Migrating from 1.x.

Configuration

Initial Setup

The assistant generates through the Apostrophe AI engine, which is configured once and shared by every AI feature in the project. Set your provider's key as an environment variable:

bash
export APOS_OPENAI_KEY=your-openai-api-key

Then configure the engine, the SEO module and the assistant in the app.js file:

javascript
import apostrophe from 'apostrophe'; ​ apostrophe({ root: import.meta, shortName: 'my-project', modules: { '@apostrophecms/ai': { options: { providers: { openai: {} } } }, '@apostrophecms/seo': {}, '@apostrophecms-pro/seo-assistant': {} } });

Important: The @apostrophecms/seo module is required for the SEO Assistant module to work. If you already have it configured in your project, you can keep the existing configuration. However, register the @apostrophecms-pro/seo-assistant module after the @apostrophecms/seo module in the app.js file — the assistant enhances the SEO meta fields, and registering it first lets the plain fields win.

If the engine already has a provider configured for another feature, the assistant uses it and there is nothing more to do here.

Choosing a Provider

Every provider adapter ships with Apostrophe — there is no extra module to install. Name the one you want under providers and set its key in the environment:

Provider Environment variable
openai APOS_OPENAI_KEY
anthropic APOS_ANTHROPIC_KEY
google APOS_GEMINI_KEY
openai-compatible APOS_OPENAI_KEY

To read a key from a variable you already use under another name, name that variable in the entry:

javascript
'@apostrophecms/ai': { options: { providers: { openai: { envKey: 'OPENAI_API_KEY' } } } }

An apiKey may also be set directly on the entry, though an environment variable is preferable in production. Configuring more than one provider additionally requires naming the default with the engine's own provider option.

Generation Options

By default the assistant follows the engine's routing. Four optional settings steer it, all in the engine's own vocabulary:

Option Effect
effort The routing level to resolve, such as low, medium or high
provider and model Pin one model, bypassing the routing table. Required together
reasoning Override the resolved reasoning level, in the provider's vocabulary
javascript
import apostrophe from 'apostrophe'; ​ apostrophe({ root: import.meta, shortName: 'my-project', modules: { '@apostrophecms-pro/seo-assistant': { options: { effort: 'low' } } } });

Writing a meta title or description is a short, inexpensive task, so effort: 'low' is usually the right choice.

These options are checked when the site starts: an effort level or a model that resolves to nothing, or a provider given without a model, stops the boot with a message naming the problem.

Offline Development

Set APOS_AI_MOCK=1 to answer every AI call with a placeholder — no provider, no key and no network needed. Everything else runs unchanged, so the assistant works normally in the editor:

bash
APOS_AI_MOCK=1 npm run dev

Content Length Restrictions

Set a minimum page context length to ensure quality suggestions:

javascript
import apostrophe from 'apostrophe'; ​ apostrophe({ root: import.meta, shortName: 'my-project', modules: { '@apostrophecms-pro/seo-assistant': { options: { minContextLength: 100 } } } });

When minContextLength is set, the module will not generate metadata for pages with text content shorter than the specified value. The default is no restriction.

Page Context

The assistant builds its page context with the Apostrophe core schema content extraction, which walks the document and collects the text of every field that participates — including text inside areas, widgets, arrays and objects. The SEO meta fields are left out, so the assistant never sees the metadata it is about to generate.

To keep a field out of the page context, tag it with the seo extraction tag, alongside the tags its field type already carries:

javascript
// modules/@apostrophecms/my-piece-type/index.js export default { fields: { add: { internalNote: { type: 'string', extractable: [ 'seo' ] } } } };

Use extractable: false instead to remove the field from extraction entirely — that hides it from every consumer, translation included.

Using the SEO Assistant

The SEO Assistant provides a field indicator UI for the SEO Title and Description fields in the page/piece editor. When you click the indicator, the module offers an action to generate metadata suggestions based on your current page content and pre-configured AI prompt.

Workflow:

  1. Click the SEO Assistant indicator next to title/description fields
  2. Review the AI-generated suggestion
  3. Accept the suggestion (and edit if needed), retry, or customize the prompt

SEO Assistant Interface

Context Menu: SEO Assistant context menu

Generated Suggestion: SEO Assistant suggestion

Custom Prompt Editing: SEO Assistant edit prompt

Applied Suggestion: SEO Assistant use the suggestion

Customizing the Prompts

Three prompts shape what the assistant asks for. The simplest way to change them is to override the phrases themselves, which are ordinary i18n strings in the aposSeoAssistant namespace:

Key What it is
promptSystem The system prompt, framing the model's role
promptEditorSeoTitle The instruction for seoTitle, which editors see and may rewrite
promptEditorSeoDescription The instruction for seoDescription
promptContext The line introducing the page context
javascript
// modules/@apostrophecms/i18n/i18n/aposSeoAssistant/en.json { "promptSystem": "You write SEO metadata in the brand voice of Example Co." }

For anything the wording alone cannot express, the prompts are module methods, so a project extends them like any other:

javascript
// modules/@apostrophecms-pro/seo-assistant/index.js export default { extendMethods(self) { return { // Return the instruction an editor sees and may rewrite getEditorPromptFor(_super, req, field) { if (field.name === 'seoTitle') { return 'Write a punchy title for this page, 60 characters at most.'; } return _super(req, field); }, // Turn that instruction and the page context into the call the // engine makes: a system prompt and the messages generatePrompt(_super, req, { context, field, editorPrompt }) { return _super(req, { context, field, editorPrompt }); } }; } };

generatePrompt returns { system, messages } — the options apos.ai.generate is called with, alongside the generation options. Override generateSuggestion to take over the call itself.

Customizing the Interface

Custom Icons and Labels

SEO Assistant context menu

You can customize the icon of the button used to trigger the assistant in the SEO Assistant module. The new icon and optional label and class properties are passed through the triggerAssistantButton option:

javascript
import apostrophe from 'apostrophe'; ​ apostrophe({ root: import.meta, shortName: 'my-project', modules: { '@apostrophecms-pro/seo-assistant': { icons: { 'panda-icon': 'Panda' }, options: { provider: 'openai', triggerAssistantButton: { icon: 'panda-icon', label: 'Generate with AI', class: ['blue-yellow'] } } } } });

By using the class blue-yellow, you can add the following CSS selector to customize the trigger assistant button.

Add custom CSS for button styling:

css
.apos-button.apos-button--blue-yellow { background-color: yellow; border: 4px solid blue; }

Meta Label Position

The SEO tab of the home page editing modal showing the metalable on the right side

You can position the meta label associated with the icon on the left (default) or right side.

javascript
// modules/@apostrophecms/seo-assistant-doc-type/index.js export default { options: { seoAssistantMetaPosition: 'right' } };

The label text itself is the triggerAssistantButton label option, described in Custom Icons and Labels above.

Migrating from 1.x

Version 2 moves the assistant onto the Apostrophe AI engine (apos.ai), configured once and shared by every AI feature in the project. The OpenAI client this module used to carry is gone, along with its provider registry.

If the engine is already configured for another feature, only step 2 applies to you.

1. Configure the engine. Add a provider to app.js:

javascript
'@apostrophecms/ai': { options: { providers: { openai: {} } } }

The openai entry reads APOS_OPENAI_KEY. To keep the OPENAI_API_KEY variable you already have, name it in the entry instead of renaming the variable:

javascript
providers: { openai: { envKey: 'OPENAI_API_KEY' } }

2. Remove @apostrophecms-pro/seo-assistant-openai from app.js, and delete modules/@apostrophecms-pro/seo-assistant-openai/ if your project has a copy of it. The module still exists so that an existing entry does not fail the boot, but it does nothing beyond saying so.

3. Remove the provider option from @apostrophecms-pro/seo-assistant. It named a provider module, and no longer does anything. The name is now the engine's: set provider and model together to pin one model, as described in Generation Options.

4. Move the model option, if you set one. It lived on the OpenAI module; the equivalent is an effort level, or a provider and model pair, on @apostrophecms-pro/seo-assistant.

A misconfiguration left over from 1.x is reported when the site starts, with the configuration to paste — nothing fails silently or waits until an editor clicks.

Custom providers

The registerProvider registry is gone: the engine decides which service answers, and the assistant asks it. A custom provider module written against the 1.x interface has two paths forward, depending on what it did:

  • A different AI service belongs in the engine, as a provider adapter — it then serves every AI feature, not just this one.
  • Different prompts or a different call is now a method override on this module: see Customizing the Prompts.

Note that generatePrompt changed shape along the way. It returned an array of chat messages; it now returns { system, messages }, the options the engine is called with, and the page context and the editor's instruction travel as a single user message rather than two.


Made with ❤️ by the ApostropheCMS team. Ready to transform your SEO workflow? Upgrade to ApostropheCMS Pro today! ⚡