How to Add Open Graph Tags to a Laravel Website
Add Open Graph tags to a Laravel site with Blade or Laravel Head. Set page-specific metadata, render it safely, and check the HTML before sharing.
Put Open Graph tags inside the shared Blade layout’s <head>, then provide page-specific values from the view or controller. At minimum, emit og:title, og:type, og:image, and og:url. Add a description and descriptive image alt text where available. Use Blade’s escaped {{ }} output for dynamic metadata.
This guide covers a direct Blade implementation and Laravel Head, how to choose and verify values, common failure cases, and a screenshot option for checking the rendered page. The examples are implementation patterns; adapt route names, model fields, and package APIs to your application.
1. What Open Graph tags do and where they go
Open Graph metadata describes a page to systems that consume the Open Graph protocol. In a Laravel application, place the tags in the HTML response’s document head so they are present in the server-rendered markup. A typical place is a shared layout such as resources/views/layouts/app.blade.php.
The protocol’s four basic properties are og:title, og:type, og:image, and og:url. Common additions are og:description and og:site_name. For an image, og:image:alt should describe the image rather than serve as its caption. [Open Graph protocol]
| Property | Purpose | Typical value |
|---|---|---|
og:title |
The page or object title | Post title or a social-specific title |
og:type |
The kind of object | website for a general page; article for a post |
og:image |
Image representing the page | Absolute, publicly reachable image URL |
og:url |
Canonical identifier for the page | Canonical public URL for that page |
og:description |
Short page summary | A plain-text summary |
og:image:alt |
Description of the image | Meaningful image description |
The protocol permits additional properties such as og:locale, og:determiner, og:site_name, and image fields including og:image:secure_url, og:image:type, og:image:width, and og:image:height. Add fields only when you have accurate values. If a page provides multiple og:image tags, the first is preferred in conflicts; keep structured image fields next to the image they describe. [Open Graph protocol]
2. Add defaults to a shared Blade layout
Start with one set of defaults in the layout. Child views can override them with Blade sections. The layout should still produce sensible metadata for pages that do not define social-specific values.
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<title>@yield('title', config('app.name'))</title>
<meta property="og:title" content="@yield('og_title', config('app.name'))">
<meta property="og:type" content="@yield('og_type', 'website')">
<meta property="og:url" content="@yield('og_url', url()->current())">
<meta property="og:description" content="@yield('og_description', '')">
<meta property="og:image" content="@yield('og_image', asset('images/social-default.jpg'))">
<meta property="og:image:alt" content="@yield('og_image_alt', '')">
</head>
<body>
@yield('content')
</body>
</html>
This example uses a public default image at public/images/social-default.jpg; add the file or change the path. The asset() helper produces an asset URL based on the application configuration. For social metadata, use fully qualified, publicly reachable URLs for the page and image. Verify that the configured host is the public HTTPS host expected for the site.
Laravel’s {{ }} Blade echoes are escaped through htmlspecialchars. Keep that escaping for metadata values, especially values originating from users or a content database. Avoid {!! !!} for metadata. [Laravel Blade documentation]
3. Provide different values for each page
A child view can provide sections before the layout renders. For a post page, use the post’s social title and image when present, and fall back to ordinary content fields when appropriate.
@extends('layouts.app')
@section('title', $post->title)
@section('og_title', $post->social_title ?: $post->title)
@section('og_type', 'article')
@section('og_url', route('posts.show', $post))
@section('og_description', $post->meta_description ?: Str::limit(strip_tags($post->body), 200))
@section('og_image', $post->social_image_url ?: asset('images/social-default.jpg'))
@section('og_image_alt', $post->social_image_alt ?: $post->title)
@section('content')
<article>
<h1>{{ $post->title }}</h1>
{!! $post->body !!}
</article>
@endsection
The route name and model properties are illustrative. The unescaped body output shown is separate from the metadata and is appropriate only if the body is trusted or sanitized according to your application’s content policy. The OG fields themselves remain escaped by Blade.
Alternatively, explicitly prepare metadata in the controller and pass it to the view. This can make fallback rules easier to inspect and reuse.
public function show(Post $post)
{
return view('posts.show', [
'post' => $post,
'ogTitle' => $post->social_title ?: $post->title,
'ogDescription' => $post->meta_description,
'ogUrl' => route('posts.show', $post),
'ogImage' => $post->social_image_url ?: asset('images/social-default.jpg'),
'ogImageAlt' => $post->social_image_alt ?: $post->title,
]);
}
Use whichever pattern fits the project, but keep a clear precedence rule: page-specific value, then site default. Laravel documents returning views and passing data through the view helper. [Laravel views documentation]
4. Use Laravel Head for structured metadata
For a site that needs shared defaults, runtime or route-specific values, and field-level precedence, Laravel Head is a documented alternative. The current Laravel 13.x documentation describes compatibility with Blade, Livewire, and Inertia. Check the package guidance against your actual Laravel version before installing.
composer require laravel/head
Define defaults in a service provider, set page values when loading the page, and render the resolved tags in the layout:
use Laravel\Head\Enums\OgType;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
// In a service provider boot method:
Head::defaults(fn (HeadBuilder $head) => $head
->title(config('app.name'))
->description('')
->og(siteName: config('app.name'), type: OgType::Website));
// In a controller action for one post:
Head::title($post->title)
->description($post->meta_description)
->og(type: OgType::Article, title: $post->title)
->ogImage($post->social_image_url, alt: $post->social_image_alt ?: $post->title);
return view('posts.show', ['post' => $post]);
<head>
<meta charset="utf-8">
@head
</head>
Laravel Head resolves defaults, route metadata, runtime metadata, and error metadata by precedence, replacing values field by field. Its @head directive renders synchronously, so define metadata before the layout is rendered. [Laravel Head documentation]
| Choose | When it fits | Check |
|---|---|---|
| Direct Blade tags | A small implementation with explicit HTML and a few shared defaults | Each page supplies values or falls back correctly |
| Laravel Head | A project that benefits from metadata defaults, resolution rules, or documented framework integrations | Installed version compatibility and values defined before synchronous rendering |
This is a comparison of documented implementation capabilities, not a performance comparison.
5. Choose image and URL values carefully
- Canonical page URL: Set
og:urlto the public canonical URL for the page, not an internal route name or a URL with temporary query parameters. If the application is behind a proxy, confirm Laravel generates the public scheme and host. - Reachable image: Use an absolute image URL that the intended consumer can fetch without a session, private network access, or browser-only state. A local filesystem path is not a URL.
- Image description: Provide meaningful
og:image:alttext that describes what the image conveys. Do not use it as a caption. - Fallbacks: Have a known default title, description, and image for records with missing optional fields. A missing image can leave the preview without the expected artwork.
- One intended primary image: If you emit several image roots, put the preferred one first and follow it with its structured properties.
- Accurate type: Use
articlefor an article-like object andwebsitefor a general site page, as appropriate to the protocol vocabulary.
6. Verify the rendered response
- Load a representative page through the same public route visitors use.
- Inspect the response HTML source or fetch the response and search within the document’s
<head>. - Confirm exactly which values were rendered: title, type, canonical URL, image URL, description, and image alt text.
- Check that dynamic characters such as quotation marks and ampersands are HTML-escaped in attributes and still represent the intended value when parsed.
- Open the page and image URLs from a public context to confirm they resolve. Check redirects, authentication requirements, and scheme or host mismatches.
- When diagnosing a preview on a particular social platform, use that platform’s current preview/debugging tool and documentation. Platform fetching and cache-refresh behavior vary; these Laravel and protocol sources do not promise when a given platform will fetch or refresh metadata.
A screenshot can help confirm what a browser visibly renders, but it does not replace inspecting the HTML head: metadata may be correct even though it is not visible in the page body. For repeatable visual checks, ScreenshotNeo is a website screenshot API and MCP server; its screenshot result is useful for checking rendered appearance while source inspection confirms metadata.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No preview image appears | og:image is absent, relative, inaccessible, or points to the wrong record. |
Render an absolute public image URL, open it independently, and verify the page’s final HTML contains it. |
| Every page has the same title or image | Only layout defaults are present, or child sections are named differently from the layout yields. | Match each @section to its @yield; inspect a response from two different routes. |
| The wrong URL is shown | The current request URL includes an alias, query string, incorrect host, or proxy-derived scheme. | Set the canonical route URL explicitly and check trusted proxy and app URL configuration. |
| Metadata contains broken quote or ampersand characters | Values were manually concatenated into HTML or escaped incorrectly. | Use normal escaped Blade interpolation and avoid pre-encoding values. |
| Metadata is empty for a post | Optional fields are null and no fallback is supplied. | Define field-by-field fallbacks to the post title, excerpt, and site default image. |
| Blade output appears in the body but not the head | The section is declared after an include/layout path that does not yield it, or metadata is set after rendering starts. | Ensure the child extends the expected layout and define metadata before the layout is rendered. |
| Laravel Head emits defaults instead of page values | Values were set too late, the wrong API/version is installed, or another precedence layer overrides them. | Set metadata in the request flow before returning the view; verify package version and resolution guidance. |
| Browser preview looks right but a sharing preview does not | The platform may fetch a different response, may not execute client JavaScript, or may retain cached data. | Ensure tags are in server-rendered HTML, verify the public response, and consult the target platform’s current tooling for fetch and refresh behavior. |
| Output includes unsafe markup in a meta attribute | Unescaped Blade output was used or values were assembled manually. | Use {{ }} for metadata and validate content at input boundaries; keep intentionally unescaped rendering confined to separately sanitized page content. |
8. Performance, reliability, and cost
Direct Blade metadata adds a small amount of server-rendered HTML and does not require a browser-side metadata update. Laravel Head adds a Composer dependency and a metadata resolution step; choose it for its structured defaults and precedence rather than assuming it is faster. The cited documentation provides no comparative benchmark.
Keep metadata generation deterministic for a given page and ensure the controller has the fields it needs without avoidable repeated lookups. Set reliable defaults so missing optional content does not result in blank tags. The largest operational reliability issue is often outside Blade: whether the page and image are publicly reachable and whether the configured canonical host is correct.
There is no special runtime service cost for emitting ordinary tags beyond serving the page. Laravel Head is a Composer package; its own package and compatibility requirements apply. Testing via a screenshot service may have its own plan and billing rules; do not treat a screenshot as proof that a social platform has fetched the page.
9. Or skip the browser setup
If the goal is a rendered screenshot for a visual check, ScreenshotNeo can return an image with one GET request. This does not inspect Open Graph values in the source; use the HTML verification steps above for that.
ScreenshotNeo API documentation
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Replace the sample URL with your public Laravel page. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000, and every feature is on every plan. See ScreenshotNeo for details and the API docs for options.
Sign up free for 1,000 screenshots a month, with no card required.
10. FAQ
Do I need a Laravel package to add Open Graph tags?
No. Ordinary Blade tags in a shared layout are enough for a straightforward site. Laravel Head is an option when its defaults and resolution model help your project.
Should these tags be added with JavaScript?
For this server-rendered Laravel setup, render them in the HTML response head. That makes the metadata present in the page source without relying on a later browser update.
Does correct Laravel markup guarantee a social preview?
No. The page and image must be reachable by the consumer, and each platform controls how it fetches and presents metadata. Verify the target platform’s current behavior separately.
Can I use the same image alt text as the image caption?
They serve different purposes. Open Graph image alt text describes the image; use a caption in the page content if readers need one.


