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

Opengraph Laravel Laravel Package

christian98/opengraph-laravel

A Laravel package for adding Open Graph meta tags to your application. Generate og:title, og:description, og:image and related social tags with ease, so shared links display rich previews on Facebook, Twitter, and other platforms.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Pull the package in. It lives on GitLab rather than Packagist (zero stars, zero dependents), so a plain composer require christian98/opengraph-laravel may or may not resolve. If it doesn't, add a VCS repository to composer.json, then require it explicitly:

{
  "repositories": [
    { "type": "vcs", "url": "https://gitlab.com/christian98/opengraph-laravel" }
  ]
}
composer require christian98/opengraph-laravel:dev-main

Pin to a commit (dev-main#abc1234) once you've vetted the source — see the Gotchas section for why.

2. Register the provider. Laravel's package auto-discovery normally handles this. If the package predates discovery or was built for an older Laravel, add the provider manually (config/app.php on Laravel ≤10, bootstrap/providers.php on Laravel 11+).

3. Publish the config. Run the publish command and pick the provider/tag from the prompt:

php artisan vendor:publish --provider="Christian98\OpenGraph\OpenGraphServiceProvider"

You should end up with something like config/opengraph.php holding site-wide defaults: site name, default image, default type, locale, maybe a Twitter handle.

4. Read four files before writing any code. The package is small, so the whole API surface is visible in:

  • README.md — usually the only docs; note the exact facade/helper/Blade names it exposes.
  • The service provider — tells you what is bound into the container and whether a facade alias or Blade directive is registered.
  • config/opengraph.php — the defaults you'll be overriding per page.
  • The Blade view or renderer — confirms whether it escapes values (this matters later).

5. First use case: a global baseline, one override. Put the render call in your <head> partial so every page gets og:title, og:description, og:image, og:url, og:type from config:

{{-- resources/views/partials/head.blade.php --}}
@include('partials.meta')
@opengraph   {{-- or <x-opengraph /> / {!! opengraph() !!}, per the README --}}

Then prove the override path on one page (a blog post or product) before rolling it out.

The facade method names used below (title(), description(), image(), url(), type()) follow the near-universal convention for this kind of package. Confirm the exact signatures against the README before copying — the shapes are stable, the casing may not be.

Implementation Patterns

Config as the floor, code as the override. Treat the published config as defaults for the 90% of pages that are static (home, about, legal). Only override on pages where the content is genuinely shareable. This keeps controllers clean and means a rebrand is a one-file change.

Per-page overrides in the controller. The most legible place to set article-specific tags is immediately before returning the view:

public function show(Post $post)
{
    opengraph()
        ->title($post->title)
        ->description(Str::limit(strip_tags($post->body), 155))
        ->image($post->og_image_url)   // must be absolute
        ->url($post->url)
        ->type('article');

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

A trait/presenter on shareable models. Once you have more than two or three content types, move the mapping onto the model so controllers stay dumb and the rules are unit-testable:

trait HasOpenGraph
{
    public function openGraphAttributes(): array
    {
        return [
            'title'       => $this->seo_title ?? $this->title,
            'description' => Str::limit(strip_tags($this->excerpt ?? $this->body), 155),
            'image'       => $this->ogImage(),          // absolute URL or null
            'url'         => $this->publicUrl(),
            'type'        => $this->ogType(),           // 'article' | 'product' | 'website'
        ];
    }
}

Then a single applyOpenGraph($model) call in the controller, or a view composer on the layout that inspects the bound model.

View composers for section-level defaults. Blog index, category pages, and paginated listings rarely deserve controller noise. Bind og:type=website and a section image once:

View::composer('blog.*', function ($view) {
    opengraph()->type('website')->image(asset('img/blog-og.png'));
});

Article-specific tags the package won't know about. Most generators only cover the core og:* set. For article:published_time, article:author, article:section, or product:price:amount, write them out alongside the generated block rather than fighting the package:

@opengraph
<meta property="article:published_time" content="{{ $post->published_at->toIso8601String() }}">
<meta property="article:author" content="{{ $post->author->name }}">

Keep these in one partial so the markup is greppable.

Localization. Pair og:locale with og:locale:alternate for each translated version, and make sure og:url points at the canonical locale URL — not whatever url()->current() happens to be when a language prefix is present.

Testing. Open Graph output is cheap to assert and easy to regress:

$response->assertSee('<meta property="og:title" content="My Post"', false);
$response->assertSee('og:image', false);

Use false for the escape flag so you're asserting raw markup, and add one test that hits a page with no override to confirm config defaults still render.

Coexistence with SEO packages. If you later add a broader SEO package that also emits og:* tags, pick one owner. The cleanest move is to let the SEO package own everything and drop this one, or disable the other's OG output — see the duplicate-tag gotcha.

Gotchas and Tips

Absolute URLs, always. og:image and og:url must be fully qualified. Storage::url() returns /storage/... unless your filesystem driver is configured with a URL — prepend url() or asset() explicitly, and verify APP_URL and ASSET_URL are correct in every environment including staging behind a proxy.

Image specs matter more than you think. Target 1200×630 (1.91:1), under ~5–8 MB, JPEG or PNG. SVG and WebP are inconsistently supported by scrapers. If your images are user-uploaded, generate a dedicated OG crop rather than reusing the hero — cropping is what crawlers do badly on your behalf.

Escaping is the #1 source of broken output. Check the Blade view: if the package escapes with e(), pass raw text and let it escape. If it doesn't, escape yourself. Double-escaping produces &amp;amp; in titles, which is visible in Slack and iMessage previews and looks unprofessional.

Duplicate tags from your old layout. Most Laravel apps already have hand-written og:title/og:description in head.blade.php. Scrapers generally take the first or last occurrence — inconsistently. Delete the hardcoded block in the same commit that adds the package.

Config caching. After editing config/opengraph.php, run php artisan config:cache locally to reproduce production, and php artisan config:clear when debugging "my change didn't apply." This is the most common false alarm.

The facade may not be aliased. On older Laravel versions, OpenGraph:: may fail with "class not found" even though auto-discovery worked. Import the full class or add the alias to config/app.php.

Behind a load balancer, force the scheme. If your app terminates TLS at the proxy, generated URLs may be http://. Add URL::forceScheme('https') in a service provider — otherwise og:url disagrees with the canonical and social crawlers may treat them as separate pages.

Scrapers cache aggressively. Facebook, LinkedIn, and Slack cache previews. When verifying, use the Facebook Sharing Debugger (re-scrape), LinkedIn Post Inspector, and Slack's cache-busting by re-pasting the URL. Bump an image version query string (?v=2) rather than waiting.

No Twitter Cards, no oEmbed. This package generates Open Graph only. If you need twitter:card, twitter:image, or JSON-LD, add them yourself in the same partial — X/Twitter does read og:* as a fallback, but the summary-card sizing rules differ.

Extension points are thin, so plan for a fork. A generator like this typically exposes only a handful of setters. Custom properties usually mean writing raw <meta> tags next to the generated block. Expect to fork if you need conditional logic (e.g., omitting og:image when a user has no avatar) — the renderer is a Blade view, so the fork is a one-file change.

Treat adoption risk as a first-class concern. Zero stars, zero dependents, no Packagist signal: audit the provider and renderer before shipping, pin to a commit, and consider vendoring the source into app/Support/OpenGraph behind a small interface. The package's real value is ~100 lines of <meta> markup — replicating it is an afternoon's work, which makes it a low-risk dependency and a low-value one. Don't build workflows around its API beyond the five core setters.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager