- PHP 62.7%
- JavaScript 37.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| admin-next | ||
| blueprints | ||
| classes | ||
| languages | ||
| blueprints.yaml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| seo-manager.php | ||
| seo-manager.yaml | ||
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,
robotscontrols (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
ImageGalleryJSON-LD entity with every detected gallery image. Route Map pages get aTouristTripentity with anitinerarybuilt 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.
How gallery/route detection works
- Subfolder Gallery: this plugin reads that plugin's own
gallery_slotsconfiguration 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.