letterncms / seo
Search and generative engine optimization: meta tags, social cards, JSON-LD, sitemap, robots and AI crawlers, llms.txt, Markdown twins, redirects and on-page checks.
Requires
- php: ^8.4
- letterncms/cms: >=0.26 <1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Search and generative engine optimization: meta tags, social cards, JSON-LD, sitemap, robots and AI crawlers, llms.txt, Markdown twins, redirects and on-page checks. Every collection with pages gets an SEO panel, and the home screen a widget with what to fix first.
Install
From Plugins → Store in the admin, or:
composer require letterncms/seo
Settings
Under Settings → SEO, kept in config/seo.yaml:
- Title Format (
title_format): the<title>of every page, from{title}and{site}. - Home Page Title (
home_title): the home page's title; the site name when empty. - Default Social Image (
default_image): shown for shared links of pages without their own. - Type, Name, Logo, Profiles (
org_type,org_name,org_logo,same_as): who the site is by, for structured data. - Summary for AI (llms.txt) (
llms_summary): who you are and what you offer, at the top of the site's/llms.txt, where SEO also gives each page its description and its Markdown twin and leaves out what search engines may not list. - Publish Markdown Versions of Pages (
markdown_twins): each page also at its address with.md. On by default. - Block These AI Crawlers (
blocked_crawlers): crawlersrobots.txtturns away. - Security Contact (
security_contact): where to report vulnerabilities; publishes/.well-known/security.txt. - Discourage All Indexing (
discourage_indexing): for staging sites.
The Redirects tab beside them edits content/redirects.yaml.
Abilities
seo/analyze: the on-page checks of one entry, with a score. Needs to view the entry's collection.seo/health: the same checks across published pages: average score, common issues and the lowest-scoring pages. Needsadmin.access.seo/markdown: the Markdown version of a page that AI crawlers receive. Needsadmin.access.redirects/list: the redirects. Needsseo.manage.redirects/create: adds one redirect. Needsseo.manage.redirects/set: puts the whole redirect list in place; what is left out is gone. Needsseo.manage.
Each says what it answers with (output). The permission seo.manage (Manage SEO Settings and Redirects) comes with the Editor role.
Hooks
seo.alternates(filter):fn (array $alternates, Entry $entry): array. The same page in other languages, each['locale' => 'fr', 'uri' => '/fr/about'], forhreflanglinks in the head and the sitemap.seo.sitemap(filter):fn (array $urls, ?string $locale): array. Addresses your plugin answers itself, each['uri' => '/shop', 'updated' => '2026-11-01'], added to the sitemap. With'locale' => 'de'an address goes in that language's sitemap, without in the default language's;$localeis the language being written,nullon a site with one.redirects.saved(action): after the redirect list changed.render.structured(action it listens to):dispatch('render.structured', string $key, array $data)while a page renders adds a node to the page's JSON-LD.
This repository is published from letterncms/plugins-public. Open issues and pull requests there.