statamic/seo-pro
SEO Pro is a comprehensive SEO addon for Statamic. It adds an intuitive SEO fieldtype for meta titles and descriptions, Open Graph and Twitter Card tags, XML sitemaps, redirects, canonical URLs, and social image support, helping you improve search visibility without leaving the control panel.
Prereqs, in the order they actually bite:
composer.json constraints and the release tag before running composer require.Install and wire the license:
composer require statamic/seo-pro
Statamic auto-discovers addons; there's no service provider to register by hand. Put the license key in .env (the STATAMIC_SEO_PRO_LICENSE_KEY-style variable the docs specify) and clear config cache. If the CP still shows an unlicensed banner, you have a cached config, not a bad key.
Publish and read the config first. It's the map of the whole package:
php artisan vendor:publish # choose the SEO Pro provider/tag
Skim config/seo-pro.php for the sitemap, redirects, reports/queue, social-preview, and cascade sections. Every behavior discussed below is downstream of a key in that file.
Attach it to content — one collection, not all of them. In the CP, open a blueprint and import the shipped SEO fieldset (or add the seo_pro fieldtype manually if you need a trimmed field set). Resist sweeping every blueprint on day one; you want one worked example before you commit the content model.
Render the head centrally. Drop the package's tag ({{ seo_pro }}, or the split variants like {{ seo_pro:meta }} where your version exposes them) into your layout <head>, and remove any hand-written <title> / <meta name="description" /> you already had. Leaving both in place ships duplicates that are invisible in the CP preview and obvious in a SERP.
First use case that proves the whole stack (15 minutes):
/sitemap.xml — the entry appears; a draft or noindexed entry does not.That loop surfaces version mismatch, license, and cache problems immediately, before you've multiplied them across ten blueprints.
Blueprint pattern: import the fieldset, don't hand-roll the fields. The shipped fieldset is what the upgrade paths assume. When you need control over which source fields feed the title/description, configure the fieldtype's source selectors rather than duplicating field definitions per blueprint — one place to change beats ten.
Lean on the cascade; don't repeat yourself. Site defaults → collection defaults → entry override. Set title/description templates and social defaults once at site level, narrow them per collection, and only fill entry fields where an editor genuinely needs a custom value. Two rules keep this sane:
Render the head once, and handle the routes SEO Pro doesn't own. Entry and term routes work because there's an implicit cascade context. Custom routes, error pages, and controller-rendered views fall back to defaults — so guard the tag and provide a fallback <title>. This is the most common "why is this page's SEO garbage" ticket on a real site.
Multisite is its own path — test it as one. Configure defaults per site rather than globally, verify canonical and hreflang output per locale, and check each site's sitemap individually. Never infer multisite correctness from a single-site smoke test: #688 (a v7.15.1 fix for editing entries without URLs in multisite) is direct evidence that this path has edge cases the single-site path never hits.
Redirect drivers: choose deliberately.
Enable auto-creation on slug change where your version exposes it; it quietly solves the "renamed the entry, broke every inbound link" problem. Bulk-import your legacy redirect map once (a seeder/migration against the active driver), then let the CP own it from there.
Sitemap: opt in per collection, not globally. Verify noindexed entries stay out. For custom routes that deserve inclusion (a product landing page, a docs section outside the collections), add them through config rather than generating a second sitemap and submitting two properties.
Queue discipline for reports. Report cost scales with entries × fields × locales. Past a few hundred entries, run report generation through the queue (deferred or background) so it doesn't block editor saves, keep a worker running, and schedule regeneration rather than relying on editors to press the button. Note that #686 is the line in the sand: under those drivers, errors were previously not recorded at all — so the driver choice now also determines whether you can see failures.
Headless / content API. SEO data lives in the entry's seo field and does not magically arrive in your API resource in the shape your front end wants. Budget an explicit work item: include the field in the resource/transformer, then map it to <title>, meta description, OG tags, and canonical in the consuming app. No patch release fixes this gap for you — it's an integration task, not a bug.
Local/staging safety: force noindex outside production via config or a globals-level switch. Two minutes of work that prevents staging outranking production.
Testing is what makes patch bumps routine. Feature-test the rendered head (assert title, description, canonical, and the og: tags for a known fixture) and snapshot the sitemap for that same fixture. With that harness, a version bump is a diff instead of a leap of faith — which matters because this addon ships frequently.
deferred/background were previously swallowed. A sudden spike in the report is a correctness fix surfacing old problems, not a regression — brief stakeholders before deploying so nobody rolls it back.lang/vendor/seo-pro, diff them on every release.failed_jobs table — the job died, not the addon.seo / seo_pro as field handles. Collisions present as confusing fallback behavior rather than errors, which is far worse to debug.php artisan config:clear && php artisan cache:clear. Half the "it's ignoring my setting" reports die here.How can I help you explore Laravel packages today?