eusonlito/laravel-meta
Laravel Meta provides a simple, fluent API for managing SEO and social meta tags in Laravel applications. Set titles, descriptions, keywords, canonical URLs, robots, Open Graph, and Twitter Card tags from controllers or views, with configuration defaults and Blade helpers.
composer require eusonlito/laravel-Meta. The service provider is auto-discovered on Laravel 5.5+, so no manual registration is normally needed.php artisan vendor:publish --provider="Eusonlito\LaravelMeta\MetaServiceProvider", or --tag=config) and open config/meta.php first. That file is where the package's real defaults live — site name, default title, description, author, robots, image — and it is the fastest way to understand what render() will emit before you set anything yourself.{!! Meta::render() !!} inside <head> in your base layout, once. That is the whole integration surface for most apps.Meta::set('title', $post->title);
Meta::set('description', Str::limit(strip_tags($post->body), 155));
Meta::set('image', $post->hero->url());
Anything not explicitly set falls back to the published config, so a fresh install renders sensible defaults immediately and you only override what differs per page.
Note on v3.3.0: the release note is a stub that only links to issue #34 and PR #23. Read those two threads before upgrading — the changelog itself tells you nothing about behaviour changes.
Config defaults + per-request overrides. Treat config/meta.php as the site-wide baseline (title suffix, default OG image, Twitter handle) and treat Meta::set() calls as the per-request delta. This keeps controllers short and means a new page is never accidentally blank.
Middleware or view composer for route-driven metadata. For content sites where the meta is derivable from the route model, a small middleware or a View::composer('*') that inspects request()->route()->parameters() is cleaner than repeating Meta::set() in twenty controllers. Guard it with $request->isMethod('GET') so POST/redirect responses don't pay for it.
Model accessors for SEO fields. If editors control titles, add getMetaTitleAttribute() that falls back to the title column, and call it from one place. Avoid letting raw column names leak into Blade.
Blade component for the head. Wrap {!! Meta::render() !!} in <x-head-meta /> alongside your other head tags (canonical, hreflang, JSON-LD). This gives you one file to audit when SEO requirements change, and keeps the layout readable.
Absolute URLs for Open Graph. og:image must be absolute. Use asset() or Storage::url()-plus-url() rather than a relative path; the package will not fix that for you and most crawlers will silently drop the image.
Localisation. In a multilingual app, set meta inside the localised request lifecycle (after App::setLocale()), and register hreflang alternates through the same mechanism so ordering and escaping stay consistent.
Caching. The rendered head is request-specific. If you cache full HTML responses, make sure the cached fragment excludes user- or page-specific meta, or key the cache on the meta inputs. Fragment caching of the <head> is almost always a bug.
Testing. Assert on the rendered string with $response->assertSee('<meta name="description"', false) in feature tests. This is the cheapest way to catch a refactor that moved Meta::set() after the view was composed.
Inertia / Livewire. The package is server-rendered only. With Inertia you need to pass meta through shared props and render it in the root Blade template; with Livewire, set meta in the initial component mount, not on subsequent AJAX updates, which never touch the document head.
{!! !!} is required — render() returns an HTML string and Blade escapes it by default. If you use {{ }} you will see literal <meta> in the page source.content. The package escapes attribute values, but if you pre-escape with e() or pass HTML through strip_tags inconsistently, you get &quot; artifacts. Pass plain strings and let the package do the escaping.set(). Decide early whether a set() is meant to override or append, and verify against the rendered output rather than assuming.@section that runs after the head is rendered. If a value is missing, check where in the render pipeline it was assigned before blaming the package.config/meta.php, run php artisan config:clear in development. A stale cached config is the most common "my change did nothing" report.scoped() binding or an explicit reset in a middleware is the fix.{!! Meta::render() !!} in isolation) or the whole tag collection before chasing config. Reading the actual emitted tags resolves most disputes faster than re-reading the docs.property pairs, JSON-LD), use the generic add-meta method rather than forking the view; keep JSON-LD in your own component so you control json_encode flags and </script> escaping.vendor:publish --force into a scratch dir and compare) — config shape changes are the most likely silent breakage in a meta package.How can I help you explore Laravel packages today?