torann/meta-tags
Meta tags manager for Laravel that adds SEO and social metadata to models and views with a fluent, configurable API. It handles titles, descriptions, Open Graph, and Twitter cards, keeping tag setup readable and centralized.
composer require torann/meta-tags:0.0.4. The package is pre-1.0 with a single tagged release from 2017; a ^0.0.4 constraint on a 0.0.x line gives you almost no protection, and floating to a later patch is how you get a silent behaviour change in your <head>.extra.laravel in its composer.json. Check vendor/torann/meta-tags/composer.json first; if it's absent, add Torann\MetaTags\MetaTagsServiceProvider::class to config/app.php and the facade alias (commonly Meta) to aliases.php artisan vendor:publish --provider="Torann\MetaTags\MetaTagsServiceProvider". This is where defaults, tag attribute conventions (name vs property), and any section/fallback structure live. Treat this file as the package's real documentation.resources/views/layouts/app.blade.php, render the package output once inside <head>. Curl the page and confirm you get exactly <meta name="description" content="...">. That specific attribute pair is what 0.0.4 fixed, so it's the right smoke test.og:title / og:description / og:image, and verify the output uses property="og:..." and not name="og:...". If OG tags come out with name, you're either on the wrong version or overriding the attribute convention in config incorrectly.@meta directive) in a single layout partial, e.g. resources/views/partials/meta.blade.php, included from every layout. Every duplicate-tag bug I've seen with this class of package comes from a second render call or a leftover hand-written <meta> block.git log on the config meaningful and avoids repeating your site name in forty controllers.$page->meta_description tend to multiply; controllers that call Meta::set(...) once are easy to grep.{page} — {tenant name}" logic.seo_title, seo_description, og_image on the model, map them in the controller with ?? fallbacks to config. Use an accessor on the model for the fallback chain so controllers stay short.App\Seo\PageMeta with forModel(), forPage(), defaults() methods that delegate to the package. Given the maturity level, this is non-optional: it makes the eventual replacement a one-file change and gives you a place to put the escaping and duplication guards you'll want anyway.<head> in feature tests. Assert on assertSee('<meta name="description"', false), assert the description content matches, and — importantly — assert the count of description occurrences is exactly 1. Ordering and duplication problems are near-invisible in a browser and obvious in an assertion.array_get, str_*, studly_case) or use a service provider pattern that predates current conventions. If you're on Laravel 9+, try the install on a branch and load a page immediately; if it explodes, a two-line shim in a forked copy is usually cheaper than finding an alternative.composer show torann/meta-tags, then php artisan config:clear and php artisan view:clear. Cached config and cached compiled Blade are the two most common reasons a change "doesn't apply."<meta name="description"> in a layout that nobody remembers adding. Grep the whole resources/views tree for <meta before you ship, and delete rather than comment out — commented-out tags come back.name vs property is the most common correctness bug. Standard meta and Twitter cards use name; Open Graph uses property. Verify each tag type in the actual output, ideally in a test, because a wrong attribute is invisible in the rendered page but silently breaks the preview.&, em dashes, and occasionally pasted HTML. Test with "quoted", it's, and a non-ASCII character. If the package doesn't escape, escape at your PageMeta wrapper boundary — never trust the input, and never {!! !!} it into the attribute.@meta is the interface you use, remember that Blade compiles directives — a stale compiled view will hold an old tag list. view:clear after changing anything structural, and prefer the plain render call in the layout if you want fewer moving parts.url()->current() or an explicit route, not relative paths — a relative canonical is worse than none.Meta::set() call in a controller is a coupling point to an abandoned package. Funnel everything through App\Seo\PageMeta so the package name appears in exactly one directory. When you outgrow it — and given 0.0.x, plan for that — you swap the internals and nothing else moves.curl -s https://app.test/page | sed -n '/<head>/,/<\/head>/p' gives you the exact bytes crawlers see, with no DevTools normalisation and no JavaScript-injected tags confusing you.How can I help you explore Laravel packages today?