How to add Open Graph tags in Laravel (Blade)
Laravel renders server-side, so scrapers see exactly what Blade produces — no hydration tricks needed. The clean pattern is the same as everywhere: social tags live in one layout, pages override the values. You do not need a package for this.
The free checker shows your previews for nine platforms — from X and WhatsApp to iMessage, Discord and Bluesky.
Add the tags to your layout
In resources/views/layouts/app.blade.php, render og:title, og:description, og:image, og:url and twitter:card from variables with sensible defaults (see the snippet). Every view that extends the layout can override them.
Pass values per page
From controllers: return view("posts.show", ["post" => $post]) and in the view set the sections/props from $post. Title from the model, description from an excerpt, image from the post or a default.
Keep URLs absolute
Use url()->current() (or route(...)) for og:url and asset("og/default.png") for images — both produce full URLs based on APP_URL. Set APP_URL correctly in production or every tag points at localhost.
Verify like a scraper
curl the page or paste the URL into the MetaPeek checker. If tags differ from the browser, look at cached views (php artisan view:clear) or middleware that varies output per user agent.
Code example
{{-- resources/views/layouts/app.blade.php --}}
<head>
<title>@yield('title', config('app.name'))</title>
<meta property="og:title" content="@yield('title', config('app.name'))" />
<meta property="og:description" content="@yield('og_description', 'Default description')" />
<meta property="og:type" content="@yield('og_type', 'website')" />
<meta property="og:url" content="{{ url()->current() }}" />
<meta property="og:image" content="@yield('og_image', asset('og-default.png'))" />
<meta name="twitter:card" content="summary_large_image" />
</head>
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('title', $post->title)
@section('og_description', $post->excerpt)
@section('og_type', 'article')
@section('og_image', $post->social_image_url)Common mistakes
- APP_URL still on localhostasset() and url() build URLs from APP_URL. In production with the wrong value, your og:image points at http://localhost — and every preview breaks.
- Escaping raw HTML descriptionsPass plain text to the meta tags. Blade escapes {{ }} output, but an excerpt full of HTML entities still looks broken on the card — strip tags first.
- Stale cached views or full-page cacheResponse caches (Varnish, Cloudflare, laravel-responsecache) can serve old tags long after the deploy. Purge after meta changes.
Frequently asked questions
Should I use a SEO package instead?
Packages like artesaos/seotools or spatie/laravel-seo are fine if you want a fluent API, but for Open Graph alone the layout pattern above is all you need — fewer dependencies, same output.
Does this work with Inertia or Livewire?
Yes — the head is still rendered by Blade server-side. For Inertia, set the values in the root Blade template from shared props; scrapers never run your JavaScript.
How do I test locally?
View-source the page (Ctrl+U) and paste the HTML into the MetaPeek paste-HTML mode — scrapers cannot reach your local environment, pasting works around that.
Paste your URL and see within two seconds whether everything is right — on every platform.