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

Open Graph Laravel Package

chriskonnertz/open-graph

A PHP library for generating Open Graph meta tags. Build and render structured og: properties for titles, descriptions, images, URLs, and more with a simple API suitable for Laravel or any PHP project, helping control how pages appear when shared on social media.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it with Composer — there is no Laravel auto-discovery, service provider, facade, or config file:

composer require chriskonnertz/open-graph

The entry point is ChrisKonnertz\OpenGraph\OpenGraph. The smallest useful flow is: create a builder, set the four spec-required properties (og:title, og:type, og:url, og:image), add optional properties, then call render() and echo the resulting <meta> string:

use ChrisKonnertz\OpenGraph\OpenGraph;

$og = new OpenGraph();
$og->title('Getting Started with Open Graph')
   ->type('article')
   ->url('https://example.com/posts/1')
   ->image('https://example.com/img/cover.jpg')
   ->description('A short summary for social cards.');

echo $og->render();

Where to look first:

  1. The README for the exact fluent method names in the version you installed (the package relies heavily on __call magic, so signatures are not obvious from IDE autocomplete).
  2. src/OpenGraph.php for the root builder and render() behavior.
  3. src/Tags/ for the typed tag groups (Basic, Article, Book, Music, Profile, Video) and the property names each one supports.

The first real use case in a Laravel app is a shared layout that renders page-specific tags. Wire a scoped binding and a view composer so every controller can contribute defaults, then override per page:

// app/Providers/AppServiceProvider.php
use ChrisKonnertz\OpenGraph\OpenGraph;

public function register(): void
{
    $this->app->scoped(OpenGraph::class, function () {
        $og = new OpenGraph();
        $og->siteName(config('app.name'))
           ->locale(str_replace('-', '_', app()->getLocale()))
           ->type('website');

        return $og;
    });
}
{{-- resources/views/layouts/app.blade.php, inside <head> --}}
{!! app(\ChrisKonnertz\OpenGraph\OpenGraph::class)->render() !!}

Do not call url()->current() inside the provider's register(); the request may not be bound yet. Set request-dependent values in a middleware or view composer instead.

Implementation Patterns

Defaults in a view composer, overrides in controllers

Keep site-wide defaults in one place and let controllers/views override only what changes:

// app/Providers/ViewServiceProvider.php
use ChrisKonnertz\OpenGraph\OpenGraph;
use Illuminate\Support\Facades\View;

View::composer('layouts.app', function ($view) {
    $og = app(OpenGraph::class);

    if (! $og->has('og:title')) {          // or track state yourself
        $og->title(config('app.name'));
    }

    $og->url(url()->current());

    $view->with('og', $og);
});

Because the same scoped instance is shared for the request, a controller can pre-populate it before the layout renders:

public function show(Post $post)
{
    app(OpenGraph::class)
        ->title($post->title)
        ->type('article')
        ->description(Str::limit(strip_tags($post->body), 160))
        ->image($post->coverUrl());

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

Encapsulate per-entity tag building

Do not scatter property calls across controllers. Put the mapping in a small class per resource so it is testable and reusable from controllers, API resources, or jobs:

namespace App\OpenGraph;

use ChrisKonnertz\OpenGraph\OpenGraph;

class PostOpenGraph
{
    public function __construct(private OpenGraph $og) {}

    public function build(\App\Models\Post $post): OpenGraph
    {
        $this->og
            ->title($post->title)
            ->type('article')
            ->url(route('posts.show', $post))
            ->description(Str::limit($post->excerpt ?? strip_tags($post->body), 160))
            ->image($post->coverUrl());

        // Typed tag group: check the README/src for the exact method names.
        $this->og->article()
            ->publishedTime($post->published_at->toIso8601String())
            ->author($post->author->name)
            ->section($post->category->name);

        return $this->og;
    }
}

Use the generic tag API for properties the typed classes don't cover

Social cards commonly need og:image:width, og:image:height, og:image:alt, og:locale:alternate, and twitter:* tags. The library focuses on Open Graph, so fall back to a generic property/tag method for these (verify the exact method name in your version):

$og->image($post->coverUrl());
$og->tag('og:image:width', '1200');
$og->tag('og:image:height', '630');
$og->tag('og:image:alt', $post->title);
$og->tag('twitter:card', 'summary_large_image');

For multiple images, determine whether repeated image() calls append or overwrite in your version. If they overwrite, build the extra tags manually with the generic method.

Rendering in Blade and caching

The builder is cheap, but you can still cache the rendered string when the underlying entity changes rarely:

$html = Cache::remember(
    "og:post:{$post->id}:".app()->getLocale(),
    now()->addHour(),
    fn () => app(PostOpenGraph::class)->build($post)->render()
);
{!! $html !!}

Always include app()->getLocale() (and any A/B or tenant dimension) in the cache key, because og:locale and translated titles differ per locale. Do not cache rendered tags for authenticated, user-specific pages.

Testing rendered output

Treat render() as HTML and assert with XPath rather than string matching. This catches escaping and duplicate-property bugs:

$html = app(PostOpenGraph::class)->build($post)->render();

$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
$xp = new DOMXPath($dom);

$this->assertSame(
    $post->title,
    $xp->evaluate('string(//meta[@property="og:title"]/@content)')
);
$this->assertSame(1, $xp->query('//meta[@property="og:type"]')->length);

Integration tips

  • Put the OG block early in <head>; most scrapers tolerate any position, but some CMS/SEO plugins do not.
  • Use absolute URLs for og:url and og:image (route(), asset(), or a filesystem disk URL), never relative paths.
  • Combine with a canonical <link rel="canonical">; the package does not emit it.
  • For SPA/meta-heavy apps, render the same tags server-side so crawlers that do not execute JavaScript still see them.
  • If you need this in multiple layouts, register the composer on a shared partial (e.g. partials.meta) instead of each layout.

Gotchas and Tips

  • Stale package. Last tagged release is 2019. It is framework-agnostic and may install on modern PHP due to loose constraints, but it is not tested against PHP 8.2/8.3, Laravel 10/11, or Octane. Run your test suite and smoke-test render() before relying on it in production; fork or wrap it if you hit deprecations.
  • No validation. The library builds whatever you give it. It will happily render a page missing og:title, og:type, og:url, or og:image, which are the four properties the Open Graph protocol expects. Add an assertion or a required check in your builder class.
  • Escaping ambiguity. Confirm whether render() HTML-escapes values in the version you install. If it does, do not pre-escape with e()/htmlspecialchars(), or you will double-encode (&amp;amp;). If it does not, escape user content yourself before passing it in. Never echo raw user input into {!! !!} without knowing which behavior applies.
  • Magic methods hide typos. Methods are delegated via __call, so a misspelled property may throw a generic exception or, worse, silently produce nothing depending on the tag class. Keep the property names in one builder class and cover them with tests.
  • Property naming and prefixes. Typed tag objects generally emit the correct og:/article:/music: prefixes for you. Generic tag()/property calls usually require the full property name (og:image:alt, not image:alt). Mixing the two styles is the most common source of malformed output.
  • Locale format. Use underscores (en_US), not hyphens (en-US). Convert from Laravel's app()->getLocale() explicitly.
  • Arrays and nested tags. Properties like images, music:album, and video:tag can be repeated. Understand whether a second call appends or overwrites; for genuinely nested structures (e.g. music:song with its own :disc/:track), the typed helpers may not cover every case, so fall back to generic tags.
  • og:type vocabulary is limited. Valid types are website, article, book, profile, and the music.*/video.* families. product is widely used in the wild but is not an official type; decide deliberately and document it.
  • Image requirements. og:image must be an absolute, publicly reachable URL. Add og:image:width, og:image:height, and og:image:alt via generic tags. Relative paths or authenticated URLs will fail silently in scrapers.
  • Do not put request state in a singleton. Use scoped (or a fresh instance) so url()->current() and per-page values do not leak between requests under Octane or long-running workers. Resolve request-dependent values in a middleware/composer, not in register().
  • Debugging. Dump the rendered HTML first, then inspect the builder's internal property array if a tag is missing. A quick dd($og->render()) in a route or php artisan tinker session is usually faster than reading the magic-method chain.
  • Extension points. Subclass OpenGraph for app-wide defaults, or add a small custom tag class/helper if you need a property family the package does not ship. Wrapping the package behind your own MetaBuilder interface makes it trivial to swap later if the package becomes unmaintainable.
  • Testing is your safety net. Because the package is old and magic-method driven, a handful of XPath-based feature tests around render() will catch upgrades, double-escaping, duplicate og:title tags, and accidental relative image URLs far earlier than a social-card preview will.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route