srg / siteseo
Manage On-Site SEO and social media tags in your database not your code.
Requires
- php: ^8.2
- illuminate/support: ^13.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
SiteSeo
SiteSeo enables your Laravel app to store SEO and social media meta tag data in the database instead of your code.
SiteSeo creates tag records identified by page route name, and renders tags in your views' head section. It ships with a self-contained admin UI for managing tags and uploading images.
Installation
composer require srg/siteseo php artisan migrate php artisan storage:link
Then set at least one admin email in .env — the admin UI is inaccessible to everyone (403) until
this is set:
SITESEO_ADMIN_EMAILS=you@example.com
See Configuration below for details.
Help Support
If you find this package useful help support my projects:
Admin UI
Once installed, tag records can be managed at /siteseo (index, create, edit, delete). The routes
are registered by SiteSeoServiceProvider behind the web, auth middleware, plus a package
middleware (EnsureSiteSeoAdmin) that restricts access to the emails configured in admin_emails
(see below).
Feature/OG/Twitter images are managed exclusively through file upload in this UI. Uploaded files are stored on the public disk under storage/app/public/siteseo/
and public URL is saved in the tag record.
Configuration
Configuration lives in packages/srg/siteseo/src/config/siteseo.php and is merged automatically via
SiteSeoServiceProvider::register() — setting the env vars below is enough, no publish step
required. Only run php artisan vendor:publish --tag=siteseo-config if you'd rather edit
config/siteseo.php directly instead of using env vars.
| Key | Env var | Default | Purpose |
|---|---|---|---|
path |
SITESEO_PATH |
siteseo |
URL path prefix for the admin CRUD routes, e.g. a/siteseo → /a/siteseo, /a/siteseo/create, /a/siteseo/{tag}/edit. Route names stay siteseo.index, siteseo.edit, etc. regardless of the path. |
admin_emails |
SITESEO_ADMIN_EMAILS |
(empty) | Comma-separated list of emails allowed into the admin UI, trimmed, matched exactly against the authenticated user's email. Empty or unset means nobody can access it — this fails closed, not open. |
Usage
Default: automatic tags for named routes (app.blade.php)
If your app uses Inertia.js without server-side rendering, do not rely on a client-side <Head>
component (Vue/React) to set meta tags on public/marketing pages. Search engines will run javascript
but social media crawlers (Facebook, Twitter/X, LinkedIn, etc.) will not display the
link card.
The simplest integration is a single call in your Inertia root Blade view (the file registered as
rootView in your HandleInertiaRequests middleware) — see resources/views/app.blade.php:
If you're using blade then it can be in any layout head section.
{!! isset($seo) ? $seo->render() : \Srg\SiteSeo\Seo::fillAndRender() !!}
Overriding per-route with dynamic data
When a page needs tags built from data that isn't a Tag record — e.g. a blog post's own
title/description/image — build a Seo instance in the controller and share it as $seo so app.blade.php
picks it up instead of falling back.
use Srg\SiteSeo\Seo; public function show(string $slug): Response { $post = Post::published()->where('slug', $slug)->firstOrFail(); $seo = new Seo; $seo->title($post->meta['title']); $seo->siteName(config('app.name', '')); $seo->description($post->meta['description']); $seo->canonical($post->meta['canonical_link']); $seo->image(asset($post->featured_image)); $seo->fallback(); // fills OG/Twitter tags from title/description/canonical above view()->share('seo', $seo); return Inertia::render('public/blog/Show', ['post' => $post]); }
fallback() is the instance-level counterpart to the Tag-based fallback used by fillAndRender() —
it fills in Open Graph and Twitter tags from whatever title/description/canonical were already
set on the instance, so per-route code only needs to set the primary fields.
License
The MIT License (MIT). Please see License File for more information.