Site-wide and per-page SEO for Grav: meta tags, Open Graph/Twitter cards, JSON-LD, sitemap.xml, managed from Admin2.
  • PHP 62.7%
  • JavaScript 37.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-16 18:20:00 +00:00
admin-next Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
blueprints Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
classes Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
languages Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
blueprints.yaml Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
CHANGELOG.md Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
LICENSE Add LICENSE, git-clone install instructions 2026-08-16 18:20:00 +00:00
README.md Add LICENSE, git-clone install instructions 2026-08-16 18:20:00 +00:00
seo-manager.php Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00
seo-manager.yaml Initial commit (v1.0.0) 2026-08-16 17:27:24 +00:00

SEO Manager

A Grav 2.0 plugin that gives pages, Route Map GPS routes and Subfolder Gallery galleries proper SEO: titles, meta descriptions, Open Graph/Twitter cards, robots control, schema.org JSON-LD (including ImageGallery for galleries and TouristTrip for routes), and a live XML sitemap.

Everything is configured from Admin2. There is no public-facing settings form of any kind — global defaults live on the plugin's own Settings page, per-page overrides live on a new SEO tab on every page, and a SEO Manager page in the sidebar handles bulk edits and site-wide health reporting. The plugin only reads that configuration to decide what to render on the live site.

Install

git clone forgejo@code.hall.me.uk:david/seo-manager.git user/plugins/seo-manager

Or drop the seo-manager folder into user/plugins/ so you end up with user/plugins/seo-manager/seo-manager.php, then clear the cache:

bin/grav clear-cache

This is a manual install — like Route Map and Subfolder Gallery elsewhere in this install, there's no Composer step and no vendor/ folder.

Requires the API plugin (and therefore Admin2) to manage settings. Without it the plugin still works — front-end rendering and the sitemap don't depend on the API plugin at all — but global settings, the page SEO tab's editor experience, the SEO Manager page and the editor panel are all delivered through it.

What you get

Global settings — Admin2 → Plugins → SEO Manager

  • General — site name, title template ({title} {sep} {sitename}, with a separate template for the home page), default description, default social image, robots defaults, canonical URL handling.
  • Social — Open Graph type, locale, Twitter card defaults, Twitter/Facebook handles, and an Organization entity for JSON-LD.
  • Structured Data — master switch, WebSite/Breadcrumb toggles, the default schema.org type, and per-template overrides. Gallery and Route Map pages get their own default types automatically.
  • Sitemap — route, default priority/change-frequency, excluded templates/routes, and whether to include the Google image-sitemap extension.
  • Advanced — search-console verification codes, whether the page SEO tab is injected (and which templates it's limited to), and extra sitewide <meta> tags.

Per-page settings — the "SEO" tab

Every page gets an SEO tab (turn this off, or limit it to specific templates, under Advanced settings) with:

  • SEO title and meta description, each with a live character counter against the length search engines typically show.
  • Focus keyword (for your own reference — shown in the SEO Manager and the editor panel).
  • Canonical URL override, robots controls (noindex/nofollow/noarchive/ noimageindex), a structured-data type override.
  • Open Graph and Twitter overrides (title/description/image/type/card).
  • Sitemap controls (exclude, priority, change frequency).
  • Page-specific extra <meta> tags.

Every field falls back sensibly when left blank: SEO title → page title, description → site default → an automatic summary of the page content, image → the page's first image → the site default image.

SEO Manager — bulk editing

A dedicated sidebar page lists every routable page with its resolved SEO status (not just what's been typed in — what will actually render), with search, template and issue filters. Select any number of pages and:

  • Turn noindex/nofollow on or off
  • Exclude from the sitemap, or set a priority/change frequency
  • Fill in a missing SEO title from a template
  • Auto-generate a missing meta description from page content
  • Clear all SEO overrides

A health summary at the top flags missing descriptions, pages with no SEO title set, noindexed pages, pages with no social image, and — importantly — duplicate titles and descriptions across the site, which is one of the most common real-world SEO problems and easy to miss page-by-page.

In-editor SEO panel

While editing a page, open the SEO panel from the editor toolbar for a live Google-style search-result preview and a pass/fail checklist (title and description length, social image present, canonical resolved, indexability). Save the page and refresh the panel to see the effect.

The live site

  • <title>, meta description/robots, canonical link, Open Graph and Twitter tags, and JSON-LD are all rewritten into the final rendered HTML on every request (onOutputGenerated) — this works with any theme, including the one already installed on this site, without editing a single template file.
  • /sitemap.xml (route configurable) is generated live from the current pages collection — no separate cache to fall out of sync, no cron job.
  • Gallery pages get an ImageGallery JSON-LD entity with every detected gallery image. Route Map pages get a TouristTrip entity with an itinerary built from the page's route names.

Turn front-end rendering off entirely (Advanced → "Render <head> tags on the live site") if you'd rather use this plugin purely as an Admin2 editing surface for SEO data and render it yourself.

  • Subfolder Gallery: this plugin reads that plugin's own gallery_slots configuration and scans those subfolders for image files — the same folders Subfolder Gallery itself uses. No changes to that plugin are required, and it doesn't need to be installed for this to be a no-op.
  • Route Map: detected from header.route_map.routes, the field that plugin writes when GPS tracks are uploaded.

Both are best-effort and loosely coupled on purpose: if either plugin is absent or a page simply has no gallery/routes, detection just returns nothing and the page gets the normal default schema type.

Known limitations

  • Per-image alt text isn't managed here — that's Grav core's own page media metadata (Admin2's Media panel), and out of scope for this plugin's first version.
  • Head rewriting parses and re-serializes the full rendered HTML with PHP's DOMDocument. That's the same technique most theme-agnostic Grav SEO plugins use (it doesn't require editing the theme), but it does mean a small amount of work happens on every request rather than being cached separately from Grav's own page cache.
  • The sitemap is built from the live pages collection on every request to it, rather than from a separately maintained cache. For very large sites you may want to put a short-lived reverse-proxy or CDN cache in front of the sitemap route.
  • Redirect management (301s for moved/renamed pages) isn't included — out of scope for this version.

How it hangs together

File Job
seo-manager.php Event wiring: blueprint injection, API routes, sidebar/panel registration, sitemap route, head rendering
classes/Resolver.php Computes the effective SEO data for a page (overrides → site defaults → computed fallbacks) — the single source of truth every other part of the plugin uses
classes/JsonLd.php Builds the schema.org @graph (Organization, WebSite, BreadcrumbList, main entity)
classes/HeadInjector.php Rewrites <head> in the final rendered HTML
classes/Sitemap.php Builds sitemap.xml
classes/Api/SeoApiController.php Admin2-facing API: page list/detail/update, bulk actions, site health
blueprints/seo-tab.yaml The per-page "SEO" tab, merged into every page blueprint
admin-next/fields/seo-counter.js Character-counter field used by the SEO title/description fields
admin-next/pages/seo-manager.js The SEO Manager bulk-editing page
admin-next/panels/seo-manager.js The in-editor SEO panel

Licence

MIT.