{"id":51429497,"url":"https://github.com/bnomei/kirby-seo","last_synced_at":"2026-07-05T03:02:10.353Z","repository":{"id":351279353,"uuid":"1203604860","full_name":"bnomei/kirby-seo","owner":"bnomei","description":"Minimal, mostly zero-config SEO plugin for Kirby CMS.","archived":false,"fork":false,"pushed_at":"2026-05-31T14:37:58.000Z","size":130,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T21:29:44.437Z","etag":null,"topics":["ai","kirby-cms","kirby-plugin","kirby5","panel-view-button","preview","robots-txt","seo","sitemap-xml","zero-config"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bnomei.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"bnomei","buy_me_a_coffee":"bnomei"}},"created_at":"2026-04-07T07:36:39.000Z","updated_at":"2026-05-31T14:37:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bnomei/kirby-seo","commit_stats":null,"previous_names":["bnomei/kirby-seo"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/bnomei/kirby-seo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fkirby-seo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fkirby-seo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fkirby-seo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fkirby-seo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bnomei","download_url":"https://codeload.github.com/bnomei/kirby-seo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fkirby-seo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35141966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","kirby-cms","kirby-plugin","kirby5","panel-view-button","preview","robots-txt","seo","sitemap-xml","zero-config"],"created_at":"2026-07-05T03:02:09.488Z","updated_at":"2026-07-05T03:02:10.346Z","avatar_url":"https://github.com/bnomei.png","language":"PHP","funding_links":["https://github.com/sponsors/bnomei","https://buymeacoffee.com/bnomei","https://www.buymeacoffee.com/bnomei"],"categories":[],"sub_categories":[],"readme":"# Kirby SEO with AI\n\n[![Kirby 5](https://flat.badgen.net/badge/Kirby/5?color=ECC748)](https://getkirby.com)\n![PHP 8.2](https://flat.badgen.net/badge/PHP/8.2?color=4E5B93\u0026icon=php\u0026label)\n![Release](https://flat.badgen.net/packagist/v/bnomei/kirby-seo?color=ae81ff\u0026icon=github\u0026label)\n[![Discord](https://flat.badgen.net/badge/discord/bnomei?color=7289da\u0026icon=discord\u0026label)](https://discordapp.com/users/bnomei)\n[![Buymecoffee](https://flat.badgen.net/badge/icon/donate?icon=buymeacoffee\u0026color=FF813F\u0026label)](https://www.buymeacoffee.com/bnomei)\n\nMinimal, mostly zero-config SEO plugin for Kirby CMS.\n\nIt gives you:\n\n- resolved `\u003chead\u003e` metadata via a snippet or page/site methods\n- automatic `robots.txt` and `sitemap.xml` routes\n- optional Panel SEO button with status and AI actions\n- optional SERP preview integration\n- optional AI title/description generation\n\n## Requirements\n\n- Kirby 5\n- PHP 8.2+\n\n## Installation\n\n```bash\ncomposer require bnomei/kirby-seo\n```\n\nIf you also want the Panel SERP preview section:\n\n```bash\ncomposer require johannschopplich/kirby-serp-preview\n```\n\n## Zero Config\n\nFor the public SEO output, the plugin is basically zero config.\n\nAdd the snippet to your page template:\n\n```php\n\u003c?php snippet('seo/head') ?\u003e\n```\n\nThe snippet renders the resolved head tags from the current page or site. See `snippets/seo/head.php`.\n\nThe plugin also registers public routes automatically:\n\n- `robots.txt`\n- `sitemap.xml`\n\nSee `src/Kirby/SeoRoutes.php`.\n\n## Methods\n\nThe plugin registers page and site methods like:\n\n- `$page-\u003eseoHeadTags()`\n- `$page-\u003eseoPreviewTitle()`\n- `$page-\u003eseoPreviewDescription()`\n- `$page-\u003eseoCanonical()`\n- `$site-\u003eseoHeadTags()`\n- `$site-\u003eseoRobotsTxt()`\n- `$site-\u003eseoSitemapXml()`\n\nSee `src/Kirby/SeoMethodRegistry.php`.\n\n## Optional Panel Button\n\nThe SEO view button is opt-in. Add `seo` to your blueprint buttons.\n\nFor pages:\n\n```yml\nbuttons:\n  - open\n  - preview\n  - -\n  - settings\n  - languages\n  - status\n  - seo\n```\n\nFor the site blueprint:\n\n```yml\nbuttons:\n  - open\n  - preview\n  - languages\n  - seo\n```\n\nSee `tests/site/blueprints/pages/default.yml` and `tests/site/blueprints/site.yml`.\n\nWhat the button does:\n\n- opens an SEO dropdown\n- shows current indexing and sitemap state\n- lets you run AI generation\n- lets you pause/resume AI auto-refresh\n- lets you hide/allow page indexing overrides\n\nSee `src/Kirby/Panel/SeoPanelButtons.php` and `src/Kirby/Panel/SeoPanelActions.php`.\n\nThe button also hints state:\n\n- green icon when indexing and sitemap are both OK\n- red button background when the resolved SEO state has a problem\n\n## Optional SERP Preview\n\nIf you install `johannschopplich/kirby-serp-preview`, you can reuse the provided section extensions.\n\nFor pages:\n\n```yml\nsections:\n  serpPreview:\n    extends: sections/seo/serp-preview/page\n```\n\nFor the site:\n\n```yml\nsections:\n  serpPreview:\n    extends: sections/seo/serp-preview/site\n```\n\nSee `src/Kirby/SeoBlueprints.php`.\n\nThose sections preconfigure the preview to use the plugin’s SEO title and description fields, so you don’t need to wire the keys manually.\n\n## Blueprint Fields and Overrides\n\nThe plugin stores SEO values in normal Kirby content fields, which means you can show them in the Panel and override AI output with regular blueprint fields.\n\nThe core fields are:\n\n- `seoTitle` using Kirby's `text` field\n- `seoDescription` using Kirby's `textarea` field with `buttons: false`\n- `seoCanonical` using Kirby's `url` field\n- `seoImage` using Kirby's `files` field\n- `seoIndex` using Kirby's `radio` field with `Default`, `Allowed`, and `Blocked` options\n\n`seoTitle` and `seoDescription` are the fields that AI writes to. If an editor changes either value in the Panel, that saved content becomes the resolved SEO output. If AI is still set to `auto`, other content changes can trigger a refresh and regenerate those values again, so use the SEO lock action if you want to keep a manual override. `seoCanonical`, `seoImage`, and `seoIndex` are regular manual overrides.\n\nIf you want to keep your existing content tab, add the SEO fields as a `fields` section next to it:\n\n```yml\ntabs:\n  content:\n    label: Content\n    columns:\n      main:\n        width: 2/3\n        fields:\n          text:\n            type: textarea\n      sidebar:\n        width: 1/3\n        sections:\n          serpPreview:\n            extends: sections/seo/serp-preview/page\n          seoFields:\n            type: fields\n            fields:\n              seoTitle:\n                type: text\n                label: SEO title\n              seoDescription:\n                type: textarea\n                label: SEO description\n                buttons: false\n                maxlength: 320\n              seoCanonical:\n                type: url\n                label: Canonical URL\n              seoImage:\n                type: files\n                label: SEO image\n                max: 1\n                multiple: false\n                uploads: false\n                query: page.images\n              seoIndex:\n                type: radio\n                label: Search indexing\n                default: default\n                columns: 3\n                options:\n                  - value: default\n                    text: Default\n                  - value: allowed\n                    text: Allowed\n                  - value: blocked\n                    text: Blocked\n```\n\nFor the site blueprint, keep the same field definitions and switch the preview section and image query to `sections/seo/serp-preview/site` and `site.images`.\n\nKirby's `textarea` field includes built-in format buttons by default, so `buttons: false` keeps the SEO description plain. If you also want other metadata in the same area, Kirby's `tags` field is available for keyword-style input, while the SEO indexing override should stay a three-option `radio` field so editors can explicitly keep the runtime default.\n\nIf you prefer the ready-made SEO tab instead of wiring the fields manually, extend `seo/page` or `seo/site` in your blueprint. The plugin also keeps `tabs/seo/page` and `tabs/seo/site` available for compatibility.\n\n## AI\n\nAI generation is optional.\n\nDefault provider behavior:\n\n- provider: `openai`\n- model: `gpt-5-mini`\n- alternatives: `anthropic`, `gemini`\n\nSee `src/Kirby/Ai/SeoAiService.php`.\n\n### Environment Variables\n\nBy default the plugin already looks for these environment variables in closures:\n\n- `OPENAI_API_KEY`\n- `ANTHROPIC_API_KEY`\n- `GEMINI_API_KEY`\n\nSee `index.php`.\n\nIf your hosting injects those into PHP, you usually don’t need extra config.\n\n### Using `bnomei/kirby3-dotenv`\n\nIf you load secrets from `.env` files with `bnomei/kirby3-dotenv`, use the same callback pattern as in that plugin’s README: resolve secrets inside closures so they are evaluated after plugins are loaded.\n\nExample:\n\n```php\n\u003c?php\n\nreturn [\n    'bnomei.seo.ai.provider' =\u003e 'openai',\n    'bnomei.seo.ai.providers.openai' =\u003e function () {\n        return [\n            'apiKey' =\u003e env('OPENAI_API_KEY'),\n            'model' =\u003e 'gpt-5-mini',\n            'timeout' =\u003e 30,\n        ];\n    },\n];\n```\n\nYou can do the same for Anthropic or Gemini:\n\n```php\n\u003c?php\n\nreturn [\n    'bnomei.seo.ai.provider' =\u003e 'anthropic',\n    'bnomei.seo.ai.providers.anthropic' =\u003e function () {\n        return [\n            'apiKey' =\u003e env('ANTHROPIC_API_KEY'),\n            'model' =\u003e 'claude-sonnet-4-5',\n        ];\n    },\n];\n```\n\n### Custom Connectors\n\nIf you do not want one of the built-in OpenAI, Anthropic, or Gemini providers, there are two extension points:\n\n- `bnomei.seo.ai.generate` for a full custom implementation that returns the final SEO title and description\n- `bnomei.seo.ai.transport` for replacing only the HTTP transport under the built-in adapters\n\nExample:\n\n```php\n\u003c?php\n\nuse Bnomei\\Seo\\Core\\Ai\\SeoAiHttpRequest;\nuse Bnomei\\Seo\\Core\\Ai\\SeoAiHttpResponse;\n\nreturn [\n    'bnomei.seo.ai.generate' =\u003e function ($request, $model, string $languageCode, $kirby) {\n        unset($model, $languageCode, $kirby);\n\n        // Call your own inference API here and normalize the response.\n        return [\n            'title' =\u003e 'Custom title',\n            'description' =\u003e 'Custom description',\n        ];\n    },\n    'bnomei.seo.ai.transport' =\u003e function (SeoAiHttpRequest $request) {\n        unset($request);\n\n        return new SeoAiHttpResponse(\n            statusCode: 200,\n            body: ['output_text' =\u003e '{\"title\":\"Custom title\",\"description\":\"Custom description\"}'],\n        );\n    },\n];\n```\n\nIf you want a fourth named provider, add a new adapter class under `src/Kirby/Ai` and extend `SeoAiService::adapter()` accordingly. The built-in selector only maps `openai`, `anthropic`, and `gemini`.\n\n## AI Source\n\nThe AI input is not the raw `text` field by default.\n\nThe default source pipeline:\n\n- renders the page HTML\n- prefers `\u003cmain\u003e` and otherwise falls back to `\u003cbody\u003e`\n- strips images and other non-markdown-ish nodes\n- converts the remaining HTML to markdown\n\nFor site-wide generation it uses the rendered home page. See `src/Kirby/Ai/SeoAiSnapshotFactory.php`, `src/Kirby/Ai/SeoAiSourceExtractor.php`, and `src/Kirby/Ai/SeoAiHtmlToMarkdownConverter.php`.\n\nIf you want to override that, return any string from `bnomei.seo.ai.source`:\n\n```php\n\u003c?php\n\nreturn [\n    'bnomei.seo.ai.source' =\u003e function ($model, string $languageCode) {\n        return (string) $model-\u003econtent($languageCode)-\u003eget('text')-\u003evalue();\n    },\n];\n```\n\nThat gives you full control over what the LLM sees.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbnomei%2Fkirby-seo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbnomei%2Fkirby-seo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbnomei%2Fkirby-seo/lists"}