oi-lab/oi-laravel-metadata

Polymorphic SEO metadata and Open Graph management for Laravel applications

Maintainers

Package info

github.com/oi-lab/oi-laravel-metadata

pkg:composer/oi-lab/oi-laravel-metadata

Transparency log

Statistics

Installs: 39

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.2.0 2026-07-07 07:55 UTC

This package is auto-updated.

Last update: 2026-07-07 07:58:54 UTC


README

OI Laravel TypeScript Generator

OI Laravel Metadata

Latest Version on Packagist Total Downloads Tests License

A Laravel package to attach polymorphic SEO metadata, Open Graph, and JSON-LD structured data to any Eloquent model — with spatie/laravel-data DTOs, a fluent Schema.org builder, dedicated services, validation rules, head-tag rendering, a @jsonLd Blade directive, and pluggable settings integration (auto-wired to oi-lab/oi-laravel-settings).

Features

  • Polymorphic Metadata, Open Graph & JSON-LD: a single metadata, openGraph, and jsonLd record per model (morphOne)
  • Traits: opt in with HasMetadata, HasOpenGraph, HasJsonLd, or the combined HasMeta
  • DTOs: typed MetadataData, OpenGraphData, OpenGraphImageData, JsonLdData (spatie/laravel-data)
  • Fluent Schema.org builder: compose any JSON-LD node with Schema::article()->headline(...)->author(...)
  • Services & Facades: MetaService / OgService / JsonLdService (Meta / Og / JsonLd) to read, write, and render
  • Blade directives: @meta, @og, @jsonLd — render a passed model, a shared Seo::for($model) subject, or the current route model automatically
  • Head-tag Rendering: @meta / @og (or Meta::render($model) / Og::render($model)) emit escaped <meta> tags
  • JSON-LD Rendering: @jsonLd (or JsonLd::render($model)) emits <script type="application/ld+json"> blocks
  • Validation: MetadataRequest / OpenGraphRequest form requests, plus IsoLanguageRule & RobotsRule
  • Setting Integration: seeds and resolves site-wide values through a pluggable SettingStore (auto-wired to oi-lab/oi-laravel-settings, config-default fallback)

The Objects

A Metadata object:

Field Type
title string
description string
keywords string[]
author string
copyright string
language ISO code (e.g. fr, en)
revisit_after string
robots string
googlebot string

An Open Graph object:

Field Type
type string
title string
description string
url string
image object (url, width, height)

A JSON-LD object holds a list of Schema.org graphs, each rendered as its own <script type="application/ld+json"> block:

Field Type
graphs array of Schema.org nodes (Article, BreadcrumbList, Organization, …)

All three are polymorphic, with at most one per parent (enforced by a unique index on the morph columns). The single JSON-LD record can still hold several graphs, so a page can expose multiple structured-data objects.

Requirements

Installation

composer require oi-lab/oi-laravel-metadata

The package auto-discovers its service provider. Publish and migrate:

php artisan vendor:publish --tag=oi-laravel-metadata-migrations
php artisan vendor:publish --tag=oi-laravel-metadata-config
php artisan migrate

This creates the metadata, open_graphs, and json_ld tables.

Local Development

Inside the monorepo, add a path repository to your main project's composer.json:

{
    "repositories": [
        { "type": "path", "url": "./packages/oi-lab/oi-laravel-metadata" }
    ]
}

Usage

Make a Model Meta-aware

use Illuminate\Database\Eloquent\Model;
use OiLab\OiLaravelMetadata\Concerns\HasMeta;

class Page extends Model
{
    use HasMeta; // or HasMetadata / HasOpenGraph individually
}
$page->metadata;   // MorphOne — Metadata|null
$page->openGraph;  // MorphOne — OpenGraph|null
$page->jsonLd;     // MorphOne — JsonLd|null

Write Values

use OiLab\OiLaravelMetadata\Data\MetadataData;
use OiLab\OiLaravelMetadata\Data\OpenGraphData;
use OiLab\OiLaravelMetadata\Data\OpenGraphImageData;
use OiLab\OiLaravelMetadata\Facades\Meta;
use OiLab\OiLaravelMetadata\Facades\Og;

Meta::update($page, new MetadataData(
    title: 'About us',
    description: 'Who we are',
    keywords: ['team', 'company'],
    author: 'OI Lab',
    language: 'fr',
    robots: 'index, follow',
));

Og::update($page, new OpenGraphData(
    type: 'website',
    title: 'About us',
    url: 'https://example.com/about',
    image: new OpenGraphImageData('https://example.com/og.png', 1200, 630),
));

The trait helpers $page->syncMetadata(...) and $page->syncOpenGraph(...) do the same. Writes use updateOrCreate, so a parent never ends up with a second record.

Render <head> Tags

Use the Blade directives — @meta, @og, and @jsonLd:

<head>
    @meta($page)
    @og($page)
    @jsonLd($page)
</head>

Or render straight from the facades ({!! Meta::render($page) !!} / {!! Og::render($page) !!}).

Without passing $page

Set a shared SEO subject once and the directives render it with no argument — set it in a controller or a view composer:

use OiLab\OiLaravelMetadata\Facades\Seo;

Seo::for($page); // shared by @meta, @og, and @jsonLd
<head>
    @meta
    @og
    @jsonLd
</head>

When nothing is set explicitly, the subject is auto-resolved from the current route's model binding — so on a Route::get('/pages/{page}', ...) route the directives just work with zero setup. An explicit argument (@meta($other)) always wins, and auto-resolution can be turned off with the auto_resolve_subject config flag.

Meta::render() outputs description, keywords, author, copyright, language, revisit-after, robots, and googlebot tags, plus google-site-verification / google verification tags resolved from settings. Og::render() outputs the og:* tags plus og:locale, og:site_name, and fb:app_id from settings. Empty values are omitted; all values are HTML-escaped.

JSON-LD Structured Data

Attach Schema.org structured data (per the Google structured data guidelines) with the fluent Schema builder — any method call sets the matching schema.org property, and nested nodes are resolved recursively:

use OiLab\OiLaravelMetadata\Data\JsonLdData;
use OiLab\OiLaravelMetadata\Facades\JsonLd;
use OiLab\OiLaravelMetadata\Support\Schema;

JsonLd::update($page, JsonLdData::make(
    Schema::article()
        ->headline('About us')
        ->datePublished('2026-07-07')
        ->image('https://example.com/og.png')
        ->author(Schema::person()->name('OI Lab'))
        ->publisher(Schema::organization()->name('Acme')),
    Schema::breadcrumbList()->itemListElement([
        Schema::listItem()->set('position', 1)->name('Home')->item('https://example.com'),
        Schema::listItem()->set('position', 2)->name('About')->item('https://example.com/about'),
    ]),
));

JsonLd::update() accepts a JsonLdData object, a single Schema builder, or raw arrays. The single record holds a list of graphs, so a page can expose several structured-data objects at once. The trait helper $page->syncJsonLd(...) does the same.

Render one <script type="application/ld+json"> block per graph with the @jsonLd Blade directive (or JsonLd::render()):

<head>
    @meta($page)
    @og($page)
    @jsonLd($page)
</head>

@jsonLd accepts a model, a JsonLdData object, a Schema builder, or a raw array — handy for ad-hoc, non-persisted structured data:

@jsonLd(\OiLab\OiLaravelMetadata\Support\Schema::webSite()->name(config('app.name'))->url(url('/')))

The rendered JSON is encoded with flags that keep it safe inside a <script> tag (<, >, and & are hex-escaped), and a top-level @context (https://schema.org by default) is injected when the graph does not declare its own. Enable json_ld.pretty in the config to pretty-print while debugging.

Validation

use OiLab\OiLaravelMetadata\Http\Requests\MetadataRequest;
use OiLab\OiLaravelMetadata\Http\Requests\OpenGraphRequest;

public function update(MetadataRequest $request, Page $page)
{
    Meta::update($page, MetadataData::from($request->validated()));
}

The IsoLanguageRule and RobotsRule rules are reusable on their own.

Setting Integration

Seed the package defaults into your settings backend:

php artisan metadata:install-settings

Settings are read and written through a pluggable SettingStore. Install the recommended oi-lab/oi-laravel-settings (listed under suggest) and it is wired automatically. Without it, the package falls back to a generic key/value Setting model or no-ops gracefully.

This inserts the following keys (idempotently — existing keys are never overwritten):

Key Default
METADATA_FACEBOOK_APP_ID ""
METADATA_GOOGLE_SITE_VERIFICATION ""
METADATA_GOOGLE_BOT ""
METADATA_GOOGLE ""
METADATA_ROBOTS index, follow
METADATA_OG_LOCALE fr
METADATA_OG_SITE_NAME ""
METADATA_OG_TYPE website

Configure the backend in config/oi-laravel-metadata.php. Leave store null to auto-detect; the model / column keys drive the generic key/value fallback:

'settings' => [
    'store' => null, // class-string to force a specific SettingStore, else auto-detect
    'model' => App\Models\Setting::class,
    'key_column' => 'key',
    'value_column' => 'value',
    'defaults' => [ /* ... */ ],
],

When no store is present, the resolver and installer no-op gracefully and fall back to config defaults. See Advanced → Setting Integration.

Overriding Models

Resolve models through OiMetadata so your overrides apply everywhere:

// config/oi-laravel-metadata.php
'models' => [
    'metadata' => App\Models\Metadata::class,   // extends OiLab\OiLaravelMetadata\Models\Metadata
    'open_graph' => App\Models\OpenGraph::class, // extends OiLab\OiLaravelMetadata\Models\OpenGraph
    'json_ld' => App\Models\JsonLd::class,       // extends OiLab\OiLaravelMetadata\Models\JsonLd
],

AI Assistant Skills

php artisan oi:skills

Testing

composer test

License

The MIT License (MIT). Please see the License File for more information.

Credits

Olivier Lacombe - Creator and maintainer

Olivier is a Product & Technology Director based in Montpellier, France, with over 20 years of experience innovating in UX/UI and emerging technologies. He specializes in guiding enterprises toward cutting-edge digital solutions, combining user-centered design with continuous optimization and artificial intelligence integration.

Projects & Resources:

  • OI Dev Docs - Documentation for all Open Source OI Lab packages
  • OnAI - Training courses and masterclasses on generative AI for businesses
  • Promptr - Prompt engineering Management Platform

Support

For support, please open an issue on the GitHub repository.