Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Seo Tools Laravel Package

digitaldream/laravel-seo-tools

Laravel SEO Tools helps Laravel apps manage SEO metadata with meta tags, Open Graph, Twitter cards, sitemaps, robots.txt, and redirects. Configure sensible defaults and model-specific SEO through a simple, flexible API.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require digitaldream/laravel-seo-tools
php artisan vendor:publish --provider="DigitalDream\SeoTools\SeoToolsServiceProvider"
php artisan migrate

The package follows the standard Laravel package conventions, so first look at three things after publishing:

  1. config/seo.php (or similarly named) — this is where defaults, table names, and route prefixes live. Read it end-to-end before touching anything else; almost every quirk you'll hit later is a config toggle here.
  2. The published migrations — they define the seo_meta (or equivalent) table. Confirm the polymorphic or slug-based columns match your own models/routes before you seed anything.
  3. The service provider's route registration — the package typically ships its own admin routes. Grep for Route:: in the vendor directory to see exactly which URLs it exposes, because these are what you'll need to protect behind auth/role middleware.

First use case — the one this package is actually built for:

A content writer opens /admin/seo/pages, picks a page (or creates an entry keyed by URL slug/route name), fills in title, description, keywords, og_image, and saves. In your Blade layout you add the render call once:

<head>
    <title>@yield('title')</title>
    {!! seo()->forRoute(request()->route()->getName()) !!}
</head>

From that point on, every SEO change is a dashboard operation, not a deploy. That is the entire value proposition — treat it as a content-editing tool, not a technical SEO framework. It does not generate sitemaps, redirects, canonical logic, or JSON-LD by default.

If your layout is Blade-component based, wrap the render call in a single <x-seo-meta /> component so you can swap the underlying package later without touching every view.

Implementation Patterns

Route-name keying (recommended). The most robust way to bind meta records to pages is by Laravel route name (blog.show, home, pricing). This survives URL changes and keeps the admin UI meaningful to non-technical editors ("Blog post page" rather than /blog/foo). Seed the table from php artisan route:list --json once, then let editors fill in the copy.

// App\Console\Commands\SeedSeoPages
collect(Route::getRoutes())
    ->filter(fn ($r) => in_array('GET', $r->methods()))
    ->each(fn ($r) => SeoMeta::firstOrCreate(['route_name' => $r->getName()]));

Polymorphic override pattern. If the package supports a model_type/model_id pair, use it for per-record meta on top of the route-level default: the show route provides fallback meta, the specific Post provides the override. This gives you a three-tier resolution order — record → route → config default — which is what editors actually expect.

$meta = SeoMeta::forModel($post)
    ?? SeoMeta::forRoute('blog.show')
    ?? SeoMeta::defaults();

Cache aggressively, invalidate on save. Meta tags are read on every page view and written rarely. Cache per route name or per model key, and bust on the model's saved/deleted events. Without this you've added a database query to every single render, which is the most common complaint about dashboard-driven SEO packages.

// in the SeoMeta model
protected static function booted()
{
    static::saved(fn ($m) => Cache::forget("seo:{$m->cache_key}"));
    static::deleted(fn ($m) => Cache::forget("seo:{$m->cache_key}"));
}

Blade component wrapper. Don't scatter {!! seo() !!} across layouts. Wrap it once:

{{-- resources/views/components/seo-meta.blade.php --}}
@props(['model' => null])
<x-slot name="head">
    {!! seo()->forModel($model ?? request()->route()->getName()) !!}
</x-slot>

Then any page can do <x-seo-meta :model="$post" /> and get the correct resolution chain. It also gives you a single place to add canonical URLs, hreflang, or JSON-LD later.

Multi-language. If your app is localized, check whether the package keys meta by (route_name, locale). If not, add a locale column yourself and scope the resolver in a small service class — don't fork the vendor code for this. A one-file wrapper around the package's repository is cheaper than a fork.

Separation of concerns: write the output yourself if needed. The package's renderer almost certainly only emits the tags stored in the DB. For anything the package doesn't cover — canonical, robots, Open Graph type, structured data — build those directly in your layout and let the package own the plain title/description/keywords/og:image block. Duplicating og:title in both places will produce two tags and confuse crawlers.

Editor-facing UX tip. Add a "SEO preview" panel to your admin UI — a fake Google SERP snippet and a Twitter card mock — driven by the same fields. This is the single biggest adoption lever; content writers who see the result fill in the fields correctly, and it's ~30 lines of Blade on top of the existing CRUD page.

Gotchas and Tips

Check maintenance status before committing. 154 stars, zero dependents, last release April 2024 — this is a small, single-maintainer package, not a framework-level dependency. Pin the version in composer.json ("digitaldream/laravel-seo-tools": "^1.0") and be prepared to vendor it if the maintainer stops responding. Budget a day to read the source; it's small enough to fully understand, which is the main mitigation.

Admin routes are probably unprotected. Packages that ship dashboard routes usually register them unconditionally. Grep the service provider for Route:: and either (a) wrap the routes in your own middleware group via config, or (b) disable the package's routes entirely ('routes' => false in config) and re-register them yourself inside auth + an admin gate. Shipping this to production with editable SEO tags unauthenticated is a defacement vector.

route:cache and config:cache interactions. If the package reads config at boot or registers routes with closures, php artisan route:cache will fail or silently drop its routes. Test a full route:cache && config:cache && view:cache before every deploy; "works locally, broken in production" is almost always this.

HTML escaping in titles. Titles and descriptions coming from a {!! !!} render are a stored-XSS sink if any editor account is compromised or careless with a <. Sanitize on save (strip_tags is usually the right call for meta fields) rather than trusting the renderer. Also strip newlines — they break <meta content="..."> parsing in some crawlers.

Duplicate tags on nested layouts. Most Blade apps have a base layout that yields a head section and a page layout that also renders one. You'll end up with two <title> tags and wonder why Google shows the wrong one. Search the rendered HTML (curl -s https://... | grep -c '<title>') after wiring it up — it should be exactly 1.

Cache invalidation is the #1 production bug. Editors update the title, see nothing change, and file a bug. Two culprits: (a) your own Cache::remember around the meta lookup with no invalidation on save, and (b) Laravel's full-page cache / response cache, which you must also clear on save. Ship an "Edits are live immediately" behavior from day one — nobody wants to explain a cache-busting button.

Fallback ordering needs to be explicit. Decide and document: model meta → route meta → config('seo.defaults') → hardcoded app name. Ambiguous fallbacks produce empty <title> tags on pages that don't exist yet, which is worse for SEO than no override at all.

OG image handling is not what you'd expect. If the admin can upload images, verify where they land (public disk vs storage) and whether the package stores a full URL or a relative path. og:image must be an absolute URL — asset() it at render time if it stores a path.

Extension point: don't edit vendor. When you need to change the HTML output (extra attributes, property vs name, JSON-LD), override the published view via resources/views/vendor/... rather than touching vendor/. The published views are the intended customization seam here, and they'll survive a composer update while a vendor edit won't.

Debugging checklist when a tag isn't appearing:

  1. php artisan tinker → query the meta table directly for the route name. Is the row there?
  2. php artisan route:list | grep seo — are the admin routes registered?
  3. Dump the resolved meta object in a @php(dd(seo()->forRoute(...))) — is it resolving, or returning null?
  4. view:clear — published view override may be stale.
  5. Check the rendered HTML for duplicate tags, not just the missing one.

When to reach for something else. If you need sitemaps, redirect management, canonical URL management, schema.org generation, or per-locale hreflang, this package doesn't cover it. For those, pair it with a sitemap generator and hand-write the rest; or if you need all of it in one place with active maintenance, evaluate a heavier SEO suite instead. This package's job is narrow and it does that job — keep it narrow.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager