Generating Open Graph Images in Laravel with Spatie
When you share a link on LinkedIn, Twitter, or any social platform, the preview image that appears is driven by the og:image meta tag. Getting those images right has historically meant one of two things: using a third-party service, or wiring up a separate image rendering pipeline that lives outside your main application. Spatie have just released laravel-og-image, a package that takes a cleaner approach - your OG image is defined as a Blade component, right inside your existing views.
How It Works
The package works by embedding a hidden template into your page HTML using a Blade component. When a crawler requests the page, middleware injects the appropriate og:image and twitter:image meta tags into the <head>. When a crawler then fetches the image URL, the package fires up Chrome, visits the original page with a special query parameter, extracts the template, and takes a screenshot at 1200x630 pixels. That screenshot is cached to disk and served on subsequent requests.
The clever part is that the template inherits your page's existing CSS, fonts, and Vite assets automatically. There is no separate stylesheet to maintain and no font loading configuration to wrestle with. Your OG image uses the same design system as the rest of your application.
Getting Started
Install the package via Composer:
composer require spatie/laravel-og-image
The package registers its middleware automatically into the web group - no manual setup required. Add the Blade component to any view you want an OG image for:
<x-og-image>
<div class="w-full h-full bg-slate-900 text-white flex items-center justify-center">
<h1 class="text-6xl font-bold">{{ $post->title }}</h1>
</div>
</x-og-image>
That is all that is needed for the basic case. The middleware handles the meta tag injection and the image generation endpoint is registered automatically.
Using a Dedicated Blade View
For anything beyond a simple inline template, you can reference a separate Blade view and pass data to it:
<x-og-image
view="og-image.post"
:data="['title' => $post->title, 'author' => $post->author->name]"
/>
This is the approach we would reach for in practice. Keeping OG image templates in their own view files makes them easier to maintain and reuse across multiple page types. The view receives the data array as standard Blade variables, so the template itself stays straightforward.
Fallback Images
Pages that do not include the component will not receive any OG image meta tags by default. You can register a fallback in your AppServiceProvider to handle those cases:
use IlluminateHttpRequest;
use SpatieOgImageFacadesOgImage;
public function boot(): void
{
OgImage::fallbackUsing(function (Request $request) {
return view('og-image.fallback', [
'title' => config('app.name'),
'url' => $request->url(),
]);
});
}
The closure receives the full request object, so you can make the fallback as specific as you need - pulling in route parameters, model bindings, or anything else available in the request context. Return null to skip the fallback for specific routes.
Configuration and Storage
Image format, dimensions, quality, and storage location are all configurable via the facade. By default the package generates JPEG images at 1200x630 with a device scale factor of 2, producing crisp 2400x1260 renders. You can store generated images on S3 or any other Laravel disk:
OgImage::format('webp')
->size(1200, 630)
->disk('s3', 'og-images');
Image URLs are content-hashed, which means changing the template automatically invalidates the old URL. Crawlers pick up the new image on their next visit without any manual cache purging, and the URLs work cleanly with Cloudflare or any other CDN.
No Chrome on Your Server? Use Cloudflare
The package uses Chrome under the hood via spatie/laravel-screenshot, which requires Node.js and Chrome or Chromium installed on your server. If that is not practical for your setup, the package supports Cloudflare's Browser Rendering API as a drop-in alternative:
OgImage::useCloudflare(
apiToken: env('CLOUDFLARE_API_TOKEN'),
accountId: env('CLOUDFLARE_ACCOUNT_ID'),
);
This is a useful option for hosting environments where installing a full browser is not straightforward, or where you want to offload the rendering work entirely.
Pre-generating Images
By default, images are generated lazily on the first crawler request. If you want them ready in advance - for a content-heavy site where first-crawl latency matters - you can pre-generate them using an Artisan command:
php artisan og-image:generate
This is worth including in your deployment pipeline for sites where social sharing is important and you cannot afford the cold-generation delay on the first share.
Why This Matters
OG images are one of those things that often get treated as an afterthought, yet they have a real impact on click-through rates from social platforms. A well-designed, dynamic OG image that reflects the content of the specific page - the post title, the author, the category - performs meaningfully better than a single static fallback image.
Until now, implementing this properly in a Laravel application required either paying for an external service or building a custom rendering pipeline. The Spatie package removes both of those obstacles. The images are generated on your own infrastructure, the templates use your existing design system, and the caching behaviour is sensible out of the box.
We build a lot of Laravel applications where content is published dynamically and social sharing matters - blog platforms, news sites, e-commerce, and client portals. This package will feature in most of those going forward.
If you are building a Laravel application and want to discuss how to implement dynamic OG images or any other aspect of your project, get in touch with us.
