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 Meta Tags Laravel Package

fomvasss/laravel-meta-tags

A Laravel package for managing page meta tags in one place. Define titles, descriptions, Open Graph and Twitter card data, then render them in your Blade views to keep SEO and social sharing metadata consistent across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Require the package

composer require fomvasss/laravel-meta-tags

If you are on Laravel 5.5+ the Fomvasss\LaravelMetaTags\MetaTagsServiceProvider is auto-discovered. On older versions register the provider and the MetaTag facade manually in config/app.php.

2. Publish the config (and views, if provided)

php artisan vendor:publish --provider="Fomvasss\LaravelMetaTags\MetaTagsServiceProvider"

3. Look at these files first

  • config/meta-tags.php — defaults, render template, and any helper/blade settings. This is where you set site-wide values (site name, default description, default OG image) so you never have to touch them again.
  • The service provider and facade — confirm the exact public method names (set, get, render, or dedicated setters like setTitle). The README is the source of truth; method names have drifted across versions of small packages like this, so verify before writing code everywhere.

4. First use case — one layout, per-page overrides

In your main layout:

<head>
    <meta charset="utf-8">
    <title>@yield('title', config('app.name'))</title>
    {!! meta_tags()->render() !!}
</head>

In a controller:

public function show(Post $post)
{
    meta_tags()
        ->set('title', $post->title . ' — ' . config('app.name'))
        ->set('description', Str::limit(strip_tags($post->excerpt), 155))
        ->set('image', $post->cover_url)
        ->set('url', route('posts.show', $post))
        ->set('type', 'article');

    return view('posts.show', compact('post'));
}

That is 90% of real-world usage: defaults in config, one render call in the layout, per-action overrides in the controller.

Implementation Patterns

Defaults via middleware — best for values that depend only on the request (site name, OG type, canonical base):

class SetDefaultMetaTags
{
    public function handle($request, Closure $next)
    {
        meta_tags()
            ->set('site_name', config('app.name'))
            ->set('type', 'website')
            ->set('url', $request->url());

        return $next($request);
    }
}

Register it in the web group so every page has sane tags before a controller runs.

Defaults via View Composer — best when defaults depend on the route or shared data:

View::composer('layouts.app', function ($view) {
    meta_tags()->set('locale', app()->getLocale());
});

Composers run before the view renders, so controller-level sets still win if you set them later in the request.

Model-driven metadata — keep SEO logic out of controllers by putting a method on the model:

public function metaTags(): array
{
    return [
        'title'       => $this->title,
        'description' => Str::limit(strip_tags($this->body), 155),
        'image'       => $this->cover_url,
        'url'         => route('posts.show', $this),
        'type'        => 'article',
    ];
}

Then foreach ($post->metaTags() as $k => $v) { meta_tags()->set($k, $v); } in one thin controller method, or via a HasMetaTags trait shared by any publishable model.

Single partial, included once — put the render call in resources/views/partials/meta.blade.php and @include('partials.meta') from your layout. One file to edit when you add Twitter cards, schema.org, or hreflang.

Caching rendered output — for public pages, the rendered block is static per route+locale:

$meta = Cache::remember("meta:{$request->fullUrl()}", 3600, fn () => meta_tags()->render());

Pass $meta to the view and {!! $meta !!} it. Invalidate by URL tag on content updates.

Testing — feature tests are cheap and catch regressions:

$this->get(route('posts.show', $post))
    ->assertSee('<meta name="description"', false)
    ->assertSee($post->title);

Note the false second argument to assertSee — you want raw HTML matching, not escaped.

SPA / Inertia — this package is server-render only. If you are on Inertia, either render tags in the root Blade file from a shared prop, or reach for a JS-side head manager and use this package only for the initial HTML shell.

Gotchas and Tips

Maintenance risk is the biggest one. 31 stars, MIT, solo-maintained. Before adopting, check the last commit date and whether it is tested against your Laravel version. On Laravel 10/11 you may need composer require ... --with-all-dependencies or a fork. Budget half a day to read the source — it is small, and reading it is faster than debugging it later.

Raw output is mandatory. {!! meta_tags()->render() !!}, not {{ }}. If you see literal <meta ...> text in the browser, that is the cause 100% of the time.

Escape everything you feed in. The render is raw HTML by design, so any user-supplied title or description is an XSS vector. Always strip_tags() and, where relevant, e() input before it reaches set().

Timing beats cleverness. Set tags in middleware, a controller, or a view composer — all of which run before the layout renders. Setting them in a child view's @section after the layout has composed the block silently does nothing. If a tag "isn't showing up," dump meta_tags()->all() right before @include('partials.meta') to confirm ordering.

Missing tags are usually a render-order or double-render problem, not a config problem. Common pair of bugs: the partial is included in both layout and child view (duplicate tags), or included in a nested component after the layout already rendered (missing tags). Guard with @once if needed.

Config and view caches. php artisan config:cache after editing config/meta-tags.php, and php artisan view:clear when published views don't reflect changes. If behavior differs between local and production, this is the first thing to check.

Published views shadow vendor views. Once you publish, edits go in resources/views/vendor/..., not vendor/.... Editing the vendor copy works locally and vanishes on composer update.

No persistence out of the box. Tags live in memory for the request. If you need per-record metadata that editors can change, store it on the model (or a meta JSON column) and hydrate it in a controller/composer — don't expect this package to give you a metatags polymorphic table.

Extension points are thin, so wrap it. There's no event system worth relying on. The practical extension points are: publish and override the render view; register your own Blade directive with Blade::directive('meta', ...); and — most importantly — put a thin App\Support\Seo class around the helper so the rest of the codebase calls your API, not the vendor's. That way swapping to artesaos/seotools or spatie/schema-org later is a one-file change instead of a find-and-replace across every controller.

When to outgrow it. If you need schema.org JSON-LD, sitemap generation, or robots.txt management, this package won't cover it. Use it for <title>/description/OG/Twitter and pair it with a dedicated tool for the rest.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console