astrotomic/php-open-graph
Generate Open Graph meta tags in PHP with a fluent, framework-agnostic API. Build structured og: tags for titles, images, videos, audio, and more, then render valid HTML for Facebook, Twitter, and other social platforms. Works standalone or in Laravel.
composer require astrotomic/php-open-graph
There is no configuration file and no mandatory service provider — it is a plain PHP object builder, usable anywhere (Laravel auto-discovery is not required for the core static API).
First place to look is src/OpenGraph.php — it is the factory / entry point. From there the tree splits into:
Types/ — Website, Article, Book, Profile, Music, Video (each sets og:type for you).Image, Video, Audio, etc.) returned by the corresponding methods.Minimal first use case — a layout <head>:
use Astrotomic\OpenGraph\OpenGraph;
$og = OpenGraph::website(config('app.name'))
->title('Page title')
->description('One-sentence summary for social cards.')
->url(request()->url())
->image(asset('img/og-default.jpg'));
echo $og; // emits <meta property="og:..."> tags
If your layout renders {{ $og }} and you see escaped HTML (<meta ...), switch to {!! $og !!} (or confirm the object implements Htmlable). The builder's __toString() returns the full meta-tag block.
Start with website for static pages, article for blog/news, profile for user pages. Everything else is optional richness.
Build in the controller, render in the layout. Pass a fully-built OpenGraph instance to the view and echo it once in <head>. This keeps templates free of @php blocks and makes the tags unit-testable.
public function show(Post $post)
{
$og = OpenGraph::article($post->title)
->description(Str::limit($post->excerpt, 200))
->url(route('posts.show', $post))
->locale('en_US');
$og->image($post->cover_url)
->width(1200)
->height(630)
->alt($post->title);
$og->publishedTime($post->published_at)
->modifiedTime($post->updated_at)
->author($post->author->name);
return view('posts.show', compact('post', 'og'));
}
Default + override pattern. In layouts/app.blade.php, provide a fallback so every page has at least og:title, og:type, og:url, og:image:
{!! $og ?? OpenGraph::website(config('app.name'))
->description(config('app.tagline'))
->url(request()->url())
->image(asset('img/og-default.jpg')) !!}
Reusable factory / view composer. For larger apps, add a small service (or a View::composer('layouts.app', ...)) that assembles the builder from the current route, model, and locale. This is where you centralize image sizing rules and absolute-URL resolution.
Structured properties are sub-builders. ->image($url) returns an Image object; configure it immediately:
$og = OpenGraph::website('Title')->url($url);
$og->image($url)->width(1200)->height(630)->alt('Cover');
$og->image($url2); // call again for a second image
Calling ->image() multiple times adds multiple og:image sets, which is allowed and useful for galleries.
Localization. Set ->locale('de_DE') and ->alternateLocale('en_US') on multi-language sites; keep the primary locale first.
Testing. The rendered output is a stable string, so assert with assertStringContainsString('og:title', (string) $og) or snapshot the block in a feature test.
Compose with other tools. This package only owns Open Graph. Pair it with Twitter Card / JSON-LD output from a separate concern — do not try to bend it into a general SEO library.
->image($url)->width(...)->height(...) is fine, but the object returned from ->width() is still the Image, not the root type. If you then need ->description(...), keep a reference to the root $og first. This is the most common source of BadMethodCallException / "method does not exist" errors.publishedTime, section, tag, author) will fail on Website. Choose the factory method first; the type is not switchable afterwards.og:title, og:type, og:image, og:url, but the builder will happily render an incomplete set and many scrapers will silently drop the card. Enforce your own checklist in the default layout or a view composer.->image('cover.jpg') produces a relative URL that Facebook/LinkedIn/Slack may refuse. Always pass absolute URLs (asset(), Storage::url(), CDN base).{{ $og }} escapes. Use {!! $og !!} (or verify Htmlable), and never pass unescaped user input as raw content yourself — the builder escapes attribute values, but double-check titles/descriptions for HTML entities.width/height often downgrades the preview to a thumbnail strip.__toString() output (fragment caching, full-page cache), tags can go stale after model edits — key the cache on updated_at.og:* is not enough for X/Twitter; add <meta name="twitter:card"> separately or combine with a card package.Type; new structured properties follow the existing Image/Video pattern. Check the base class for the property() / render contract before adding custom tags — that is the intended extension point.How can I help you explore Laravel packages today?