24

Product support

Get help in the plugin support forum.

  • Added on Aug 13, 2026
  • Current version: 1.10.4
  • Platform Compatibility
    v4.x 1.10
    v3.x 1.10
    v2.x not tested
    v1.x not tested
  • License: Regular / Extended
  • Created by

Categories

One field instead of six. Add type: powersystems_seofields to any blueprint or model and editors get meta title, meta description, canonical URL, noindex/nofollow toggles and Open Graph title, description and image in a single field. The image picker reuses October's own Media Manager popup rather than reimplementing one.

See it before you publish. The field renders a live Google search result and a Facebook/LinkedIn share card as the editor types, with character counters on both. No more publishing to find out the description was truncated.

Render it in one line. The seoMeta Twig filter outputs the meta description, robots directive, canonical link, Open Graph tags and a Twitter Card tag. Pass fallbacks for anything the editor left blank:

{{ post.seo|seoMeta({
    title: post.title,
    description: post.summary_text,
    url: this.page.baseFileName|page,
    image: post.banner.path
})|raw }}

Values stored as raw JSON by Tailor's EntryRecord are handled automatically.

JSON-LD without a schema library. Four Twig functions build plain schema.org arrays — schemaLocalBusiness, schemaBreadcrumbList, schemaFaqPage and schemaArticle — and the schema filter wraps the result in a ready-to-output <script type="application/ld+json"> tag. Nothing is guessed or auto-filled: only your theme knows the business details, so you pass them in.

sitemap.xml, served automatically. Static CMS pages and RainLab.Pages entries are discovered from the active theme, skipping pages that are hidden, marked noindex, or have required :param placeholders in their URL. Utility paths (/api/*, /404, /components, anything ending /search or /rss) are excluded by default and the pattern list is editable. Dynamic content contributes through the powersystems.seo.sitemap.extend event.

Redirect manager with wildcards. A backend list of 301/302 redirects with an enabled toggle and a hit counter per rule. Wildcards are supported (old-blog/* → /blog/*, with the captured segment substituted), and an exact match always wins over a wildcard one. Redirects are only consulted for requests that would otherwise 404, so existing pages are never affected.

Editable robots.txt. Served at /robots.txt, edited from the backend, defaults to allowing everything and pointing at the sitemap. If a static robots.txt or sitemap.xml in the web root is shadowing the plugin's dynamic routes, SEO Health detects it and offers a one-click, reversible "import & disable" — it renames the static file to a .bak backup and pre-fills the editor from it, so nothing already published is lost. It never deletes files and never edits your .htaccess (for that it detects and tells you what to change).

Default social share image. Set one image site-wide (Settings → SEO → Social) and every page without its own share image falls back to it for og:image, so links always preview nicely on WhatsApp, Facebook and LinkedIn.

SEO Health dashboard. Audits your theme's own pages (native CMS and RainLab.Pages) automatically — the same set the sitemap lists — so it's useful the moment you install it, no wiring required. Flags missing or too-long meta titles and descriptions, missing share images, duplicate titles, and noindex pages, and shows a serving-status panel for robots.txt/sitemap.xml. Extend it to dynamic content through the powersystems.seo.health.extend event.

Pages Meta Tags: all your meta titles and descriptions on one screen. Settings → SEO → Pages Meta Tags lists every static page of your theme in a single table, so you can fill in meta titles and descriptions without opening each page in the CMS Editor. The list is read fresh from the theme on every load, so a newly created page appears automatically. Native CMS pages are editable there, RainLab.Pages entries are listed with a link through to their own screen, and pages that search engines would not show anyway (hidden, noindex or excluded) are left out. Every field shows what the page already prints as a placeholder, with a "Start from the default" link to adjust it instead of retyping it, and only the rows you changed are saved. A search box narrows long lists by page name, URL or meta text. Other plugins can add their own records (listings, categories, agents, blog posts) through the powersystems.seo.pages.extend event and supply the true fallback text through powersystems.seo.pages.fallbacks, so one screen covers the whole site.

Canonical URL helper. For themes that build their own <head> by hand, {{ canonicalUrl() }} outputs the clean canonical URL of the current page: https, no query string, and a staging subdomain mapped back to its production host.

Multisite aware. On multisite installs sitemap.xml and the Sitemap line in robots.txt are built for the hostname that made the request, so every site lists its own pages under its own domain.

Requirements

  • October CMS 3.0 or newer (3.x and 4.x)
  • PHP 8.1+ (October 4 itself requires 8.2+)

The October APIs this plugin builds on are identical across the 3.x and 4.x lines. Runtime-tested on October 3.7 / PHP 8.1 and on 4.x.

What you get

  • Every future update in the 1.x line
  • Bug fixes and October compatibility updates
  • Support by email for the plugin itself

Integrating the plugin into your own theme or blueprints is your own work; the documentation covers the wiring step by step. If you would rather not do it yourself, see below.

About Power Systems

This plugin is developed and maintained by Power Systems, an October CMS development studio. We build it for the sites we deliver ourselves, which is why it exists and why it keeps getting updates.

Beyond the plugin itself we offer:

  • Support on the plugin — included in the purchase price.
  • Customisation — adjustments so it fits the way your site actually works.
  • Custom features — extensions built specifically for your project.
  • Installation and integration on your own October CMS site, including wiring the SEO fields into your blueprints and the meta output into your theme.

Customisation, custom features and installation are paid services, quoted per project. Get in touch at support@psaruba.com.

Installation via Command Line

php artisan plugin:install PowerSystems.Seo

Installation

Add the plugin to your project on the marketplace, then install it:

composer require powersystems/seo-plugin

Or install it from the backend under Settings → Updates & Plugins. The migration that creates the redirect table runs automatically.

Configure it under Settings → Power Systems → SEO (robots.txt and sitemap) and Settings → Power Systems → Redirects. The SEO Fields widget and the JSON-LD helpers need no configuration — you only wire them into a blueprint and theme, as below.

The SEO Fields widget

Add one field to any blueprint or model:

seo:
    label: SEO
    type: powersystems_seofields

This covers meta title, meta description, canonical URL, noindex/nofollow, and Open Graph title, description and image. It stores an array:

[
    'meta_title' => '...',
    'meta_description' => '...',
    'canonical_url' => '...',
    'noindex' => false,
    'nofollow' => false,
    'og_title' => '...',
    'og_description' => '...',
    'og_image' => '/path/to/image.jpg',
]

Rendering it in a theme

Use the seoMeta filter in your layout <head>, passing fallbacks for anything an editor leaves blank:

{{ post.seo|seoMeta({
    title: post.title,
    description: post.summary_text,
    url: this.page.baseFileName|page,
    image: post.banner.path
})|raw }}

This outputs the meta description, robots directive, canonical link, Open Graph tags and a Twitter Card tag. It does not output a <title> tag — October's own meta_title/title viewBag convention already covers that for CMS pages. Content types that store the field as raw JSON (e.g. Tailor's EntryRecord) are handled automatically.

JSON-LD schema.org helpers

Four Twig functions build plain schema.org arrays; the schema filter wraps the result in a <script type="application/ld+json"> tag:

{{ schemaLocalBusiness({
    name: 'Bohemian Bar & Restaurant',
    type: 'Restaurant',
    url: 'https://example.com',
    telephone: '+297 2808448',
    address: { streetAddress: '...', addressLocality: '...', addressCountry: 'AW' }
})|schema|raw }}

{{ schemaBreadcrumbList([
    { name: 'Home', url: '/' },
    { name: post.title, url: post.url }
])|schema|raw }}

schemaFaqPage and schemaArticle follow the same pattern. Nothing is guessed — only your theme knows the business details, so you pass them in.

sitemap.xml and robots.txt

Both are served automatically and configured under Settings → Power Systems → SEO. The sitemap includes static CMS pages and RainLab.Pages entries, skipping hidden, noindex, and parameterised URLs. Contribute dynamic content (blog posts, Tailor entries) through an event:

Event::listen('powersystems.seo.sitemap.extend', function () {
    return Blog\Post::isPublished()->get()->map(function ($post) {
        return ['loc' => $post->url, 'lastmod' => $post->updated_at];
    })->toArray();
});

Redirect manager

Under Settings → Power Systems → Redirects: a list of 301/302 redirects with an enabled toggle and a hit counter. Wildcards are supported (old-blog/* → /blog/*); an exact match always wins over a wildcard one. Redirects are only consulted for requests that would otherwise 404, so existing pages are never affected.

SEO Health dashboard

Under Settings → Power Systems → SEO Health: flags missing or too-long meta titles and descriptions, missing share images, duplicate titles, and noindex pages. Feed it your dynamic content the same way as the sitemap, through powersystems.seo.health.extend.

Pages Meta Tags

Settings → SEO → Pages Meta Tags is a bulk editor for the meta title and meta description of every page, so you do not have to open each page in the CMS Editor.

  • The page list is read from the active theme on every load, the same discovery the sitemap and SEO Health use. A newly created page shows up without any registration.
  • Native CMS pages are editable. RainLab.Pages entries are listed with a link through to their own screen.
  • Pages that search engines would not show (hidden, noindex or matched by an exclude pattern) are left out.
  • An empty field shows what the page already prints as a placeholder. Click into it, or use Start from the default, to copy that text into the field and adjust it. A field you leave untouched stays empty.
  • Only the rows you changed are saved, and Ctrl+S / Cmd+S saves the screen. The search box filters by page name, URL, source or meta text, and edits you made before searching are kept.
  • Titles and descriptions are cut at a word within the recommended length when copied from a default.

Contributing records from another plugin

Records from your own plugins (listings, categories, agents, blog posts) can be edited in the same table. Listen to powersystems.seo.pages.extend and return one array per record. url, model and id are required, a row without them is ignored. title, source, meta_title, meta_description, is_hidden and meta_robots are optional.

Event::listen('powersystems.seo.pages.extend', function () {
    return Listing::all()->map(fn ($listing) => [
        'url'              => '/listing/'.$listing->slug,
        'title'            => $listing->title,
        'source'           => 'listing',
        'model'            => Listing::class,
        'id'               => $listing->id,
        'meta_title'       => (string) $listing->meta_title,
        'meta_description' => (string) $listing->meta_description,
    ])->all();
});

Saving writes the values back to the model with a normal save(), so the model needs meta_title and meta_description attributes. Each record is checked against the list again on save, and a row that was not offered is rejected. The sitemap and SEO Health still read only the theme pages, so contributed records are not listed twice.

Showing the real fallback text

When a field is empty, your templates usually still print something. Tell the editor what that is by listening to powersystems.seo.pages.fallbacks. It receives the entry list and returns a map keyed by source|model|id:

Event::listen('powersystems.seo.pages.fallbacks', function (array $entries) {
    $map = [];

    foreach ($entries as $entry) {
        if (($entry['model'] ?? null) !== Listing::class) {
            continue;
        }

        $map[$entry['source'].'|'.$entry['model'].'|'.$entry['id']] = [
            'title'       => 'The title your template prints',
            'description' => 'The description your template prints',
        ];
    }

    return $map;
});

Without a listener the editor falls back to generic suggestions.

The canonical URL helper

If your theme builds its own <head> by hand instead of using the seoMeta filter, output the canonical link with the Twig function:

<link rel="canonical" href="{{ canonicalUrl() }}">

It returns the clean URL of the current page: https, no query string, and a staging subdomain mapped back to its production host.

Multisite

On multisite installs sitemap.xml is built for the site that made the request, and the Sitemap line in robots.txt is written per hostname. Leave that line out of the robots.txt text in the settings and it is added for the host that asked. A line you wrote yourself is left exactly as written.

Adding it to a new site — checklist

  1. Add type: powersystems_seofields as a new field on the content type(s) that need SEO — don't repurpose an existing field.
  2. If the site uses Tailor, run php artisan tailor:migrate so the new field gets a column (additive, safe on existing content).
  3. Wire seoMeta into the theme's layout <head> with fallbacks.
  4. Add JSON-LD where relevant — schemaLocalBusiness on the homepage, schemaBreadcrumbList on inner pages, schemaArticle on blog templates.
1.10.4

Write the robots.txt Sitemap line per hostname. It names an absolute URL, so a hand-written one in the settings pointed every site on a multisite install at the same sitemap. Leave it out of the settings text and it is emitted for the host that asked; a line that is already there is left exactly as written.

Oct 19, 2026

1.10.3

Build the sitemap for the site the request actually asked for. A plain route runs outside the CMS, so nothing had resolved which site it was answering: on a multisite install every hostname got the primary theme's sitemap, listing the other site's pages under its own domain. Single-site installs are unaffected.

Oct 19, 2026

1.10.2

Include pages whose URL only takes an optional parameter. /news/list/:page? is a real page at /news/list and page two of it is the same page, but it was skipped along with the per-record routes like /property/:slug, so the news, listings and search-results pages could not be given a meta title anywhere.

Oct 19, 2026

1.10.1

Fix a full save reporting "saved 200 page(s), but 1 failed" and losing the rest. The screen posts around 2,650 inputs on a site this size and PHP's max_input_vars stops at 1000, silently dropping everything past it - hence exactly 200 complete rows and one truncated row arriving without its source. Only changed rows are submitted now, by taking the name off every field up front and putting it back once that row differs from what was rendered. The previous attempt hooked the form's submit event, which never fires: the Save button posts through data-request.

Oct 19, 2026

1.10.0

Rework the bulk meta editor around what the page already prints. Clicking into an empty field drops the page default in so it can be adjusted rather than retyped, and an untouched field is left empty again on the way out, so clicking through a few hundred rows stores nothing. The status keeps up with the fields as you type instead of still reading "Page default" beside something you just typed over, and an empty field now counts the default that is actually being served rather than reporting zero characters while the page is printing 277. Saving submits only the rows that changed - it used to post and rewrite every row, which meant hundreds of theme-file writes to store two fields.

Oct 19, 2026

1.9.2

Fix "Data too long" when starting from the page default. Assigning a value in code walks straight past the maxlength the browser enforces while typing, so a listing's 300-character page default went into a 255-character column and the save failed. The copied text is now cut at a word within the recommended length, and the fields carry the column width as a hard ceiling so a long paste cannot fail the same way. Ctrl+S / Cmd+S now saves the screen.

Oct 19, 2026

1.9.1

Add a "Start from the default" link under each meta field. A placeholder shows what the page says today but cannot be edited - typing replaces it wholesale - so changing three words of it meant retyping the lot. The link copies it into the field; emptying the field again hands it back to the page default and brings the link back.

Oct 19, 2026

1.9.0

Show each page's real fallback title and description as placeholders in the bulk meta editor, instead of pre-filling a generated line into the field. The old suggestion ("Learn more about X at Y.") was often worse than what the page already outputs - a listing prints the opening of its own description - and one Save all would have written that boilerplate over every record at once. A site supplies its true fallbacks through the new powersystems.seo.pages.fallbacks event; with no listener the previous pre-filled suggestions are kept. Rows from RainLab.Pages now link through to the Pages screen rather than only saying they are not editable, and the status column distinguishes "Set" from "Page default".

Oct 19, 2026

1.8.2

Rename the settings entry from "Pages Meta" to "Pages Meta Tags", which says plainly what the screen edits.

Oct 19, 2026

1.8.1

Add a search box to the Pages - Meta Title & Description screen. On a site with hundreds of pages the list was only usable by scrolling; typing now narrows it by page name, URL, source or the meta text itself. Filtering happens in the browser and rows are hidden rather than dropped, so "Save all" still submits everything and edits made before searching are never lost.

Oct 19, 2026

1.8.0

The Pages screen can be extended. Other plugins contribute their own records through the powersystems.seo.pages.extend event and they become editable in the same table, so a site with listings, categories, agents or blog posts no longer scatters its meta fields over half a dozen screens. The sitemap and the health audit still read only the theme pages, so contributed records are not listed twice.

Oct 19, 2026

1.7.7

Add a `canonicalUrl()` Twig function - the clean current-page canonical URL (https, no query string, staging subdomain stripped back to its production host), for themes that build their own <head> tags by hand instead of using the seoMeta filter. Bohemian had a hardcoded `<link rel="canonical" href="https://bohemianaruba.com...">` added directly to its theme layout to fix a Search Console "Duplicate without user-selected canonical" report - moved into the plugin as `{{ canonicalUrl() }}` so every site gets this for free instead of each theme hardcoding its own domain.

Oct 19, 2026

1.7.6

Pages Meta's save handler now rejects any row whose fileName isn't one of the theme's current pages, instead of trusting whatever a submitted form posts. Guards against a tab left open across a page rename/add/remove (or any other stale-form-state edge case) posting a mismatched fileName - previously that could reach Cms\Classes\Page::save() and fail with a raw "Invalid file name" error; now it's caught up front with a clear "not a known page, reload and try again" message and nothing is attempted.

Oct 19, 2026

1.7.5

Pages Meta now leaves out hidden/noindex pages (e.g. /404) automatically - there's nothing to gain from a hand-tuned title/description on a page search engines won't show anyway. The filter is the sitemap's own crawlable-page rule, pulled out into StaticPages::isCrawlable() so both features share one definition instead of two that could drift apart. "Use suggestion" also now focuses the field with the cursor at the end, so you can immediately keep typing to append something (e.g. a location) instead of having to click in and retype the whole suggestion yourself. The homepage's title/description suggestions no longer template its structural "Home" label into obviously-generated filler ("Home | Site Name") - the title suggestion is the site name alone, and the description suggestion is left blank rather than offer something weak, since the homepage is worth writing by hand. The Status column now says exactly what's missing ("Missing title", "Missing description", or both) instead of a bare "Missing" that read as wrong once one of the two fields was clearly filled in.

Oct 19, 2026

1.7.4

Add Settings > SEO > Pages Meta - a one-screen bulk editor for every static page's meta title & description, instead of opening each page individually in the CMS Editor. The page list is read fresh from the theme on every load (the same discovery sitemap.xml and SEO Health already use), so a newly created static page appears here automatically with no extra registration step. Only native CMS pages are editable; RainLab.Pages entries (if that plugin is installed) are listed read-only. Empty fields show a one-click "Use suggestion" fallback built from the page title and site name.

Oct 19, 2026

1.7.3

Fix sitemap.xml/SEO Health listing the bundled "demo" theme's pages instead of the site's real ones on some multisite installs. The 1.5.1 fix assumed Theme::getActiveTheme() returns nothing when it can't resolve the site outside the normal request lifecycle, then fell back to the site's configured theme - but it can instead silently return October's own fallback theme (non-null, so the correct fallback never ran), producing a sitemap full of the wrong URLs with no error. The site's configured theme is now checked first, Theme::getActiveTheme() only as a fallback.

Oct 19, 2026

1.7.2

Fix the Settings sidebar (SEO, Redirects, SEO Health, Media Trash, Administrators, and so on) disappearing on the Redirects and SEO Health pages, leaving no way to reach any other settings page from either. Both set their own 'PowerSystems.Seo' + 'seo' BackendMenu context instead of the 'October.System' + 'system' pair the sidebar partial is registered under. Same bug, found independently, as PowerSystems.MediaOptimizer 1.5.6/1.5.7.

Oct 19, 2026

1.7.1

Security fix - JSON-LD output could break out of its own script tag. Schema values come from CMS content, so a page title or FAQ answer containing "</script>" ended the tag early and let the rest be parsed as HTML, which is stored XSS on every page rendering that schema. Angle brackets are now escaped as unicode sequences (JSON_HEX_TAG), which JSON-LD consumers read identically and browsers cannot mistake for markup. An encoding failure now yields no tag instead of the literal text "false". Sites using the schema filter with content-derived values should update.

Oct 19, 2026

1.7.0

Add a site-wide Default social share image setting (Settings > SEO > Social). The seoMeta filter falls back to it for og:image when a page has none of its own, so every page gets a share preview, and SEO Health stops flagging pages for a missing share image once it's set.

Aug 04, 2026

1.6.0

SEO Health now audits the theme's own static pages (native CMS and RainLab.Pages) automatically - the same set the sitemap lists - so the dashboard is useful out of the box instead of empty until a content type registers via the event. A missing meta title on a page that still has a page title is reported as advice (it falls back to the page title) rather than a hard miss. Sitemap and audit now share one page-discovery helper.

Aug 04, 2026

1.5.2

Fix the Sitemap toggle showing as off while the sitemap was still being served. A never-set value now consistently reads as on (matching the route), so the switch and the actual behaviour agree.

Aug 04, 2026

1.5.1

Fix an empty sitemap.xml on multisite installs. The sitemap is served from a plain route (outside the CMS request lifecycle), where the active-theme lookup returns nothing because the theme is bound to the resolved site definition - so no pages were ever listed. The generator now falls back to the current/primary site's theme (then the edit theme). Single-site installs are unaffected.

Aug 04, 2026

1.5.0

Detect and safely take over a static robots.txt / sitemap.xml (or an .htaccess rule) that shadows the plugin's dynamic routes. SEO Health gains a serving-status panel with a one-click, reversible "import & disable" that renames the static file to a .bak backup - it never deletes and never edits .htaccess (that's detect-and-instruct only). The robots.txt editor now pre-fills from an existing static file so nothing already published is lost, and a Sitemap line is added automatically when the imported file doesn't already have one.

Aug 04, 2026

1.4.1

Fix "Undefined array key" (PHP 8.x) in the seoMeta filter when a record's SEO Fields value is empty/never saved - key reads are now null-safe so fallbacks render correctly.

Aug 04, 2026

1.4.0

Include RainLab.Pages static pages in sitemap.xml, filtered the same way as native CMS pages.

Aug 04, 2026

1.3.2

Support the October CMS 3.x line alongside 4.x (PHP floor lowered to 8.1). Core classes that moved between releases (MediaLibrary, ResizeImages) are now resolved at runtime instead of being imported directly.

Aug 04, 2026

1.3.1

Add wildcard support to the redirect manager (e.g. old-blog/* -> blog/*), with exact matches still taking precedence over wildcard ones.

Aug 04, 2026

1.3.0

Add an SEO Health dashboard flagging missing/too-long meta titles & descriptions, missing share images, duplicate titles, and noindex pages.

Aug 04, 2026

1.2.0

Add a live Google/social share preview (with character counters) to the SEO Fields widget, updating as you type.

Aug 04, 2026

1.1.0

create_redirects_table.php

Aug 04, 2026

1.0.0

SEO Fields widget (meta title/description, canonical, robots, Open Graph) and a JSON-LD schema.org helper library (LocalBusiness, BreadcrumbList, FAQPage, Article).

Aug 04, 2026

Upgrading

Updates install through Composer or the backend Settings → Updates & Plugins screen; database changes run automatically on update.

1.x

The 1.x line is backward compatible throughout — no breaking changes, no manual migration steps. Field data, settings, redirects and the seoMeta/schema* Twig API are stable across all 1.x releases, so you can update freely.

Version 1.3.2 lowered the minimum requirement to October CMS 3.0 / PHP 8.1; there is nothing to change on an existing install when moving to it.