PHPackages                             honeystone/laravel-seo - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. honeystone/laravel-seo

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

honeystone/laravel-seo
======================

SEO metadata and JSON-LD package for Laravel.

2.1.1(2mo ago)34744.1k↑47.2%16[1 issues](https://github.com/Honeystone/laravel-seo/issues)MITPHPPHP ^8.2

Since Jul 26Pushed 2mo ago6 watchersCompare

[ Source](https://github.com/Honeystone/laravel-seo)[ Packagist](https://packagist.org/packages/honeystone/laravel-seo)[ Docs](https://honeystone.com)[ RSS](/packages/honeystone-laravel-seo/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (28)Versions (16)Used By (0)

Honeystone SEO Configurator for Laravel
=======================================

[](#honeystone-seo-configurator-for-laravel)

[![Static Badge](https://camo.githubusercontent.com/f3d59aabff905e2e09d3e1b80623b50094f8ab04aa7f9ed4e8e3b9b802d360df/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d70617373696e672d677265656e)](https://camo.githubusercontent.com/f3d59aabff905e2e09d3e1b80623b50094f8ab04aa7f9ed4e8e3b9b802d360df/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d70617373696e672d677265656e)[![GitHub License](https://camo.githubusercontent.com/7f0b317a5da27f50357950063b835beb4679f504b7bef38b700c698246e4628e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f686f6e657973746f6e652f6c61726176656c2d73656f)](https://camo.githubusercontent.com/7f0b317a5da27f50357950063b835beb4679f504b7bef38b700c698246e4628e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f686f6e657973746f6e652f6c61726176656c2d73656f)[![Latest Version on Packagist](https://camo.githubusercontent.com/37f7a5afd4a09b7974d7999a5f9c8f147e2ea72e2161d75ac80d53371493ea75/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f686f6e657973746f6e652f6c61726176656c2d73656f)](https://packagist.org/packages/honeystone/laravel-seo)[![Packagist Dependency Version](https://camo.githubusercontent.com/21d9ec171edb00d88631db4817f95cc19532b24de066d914b27ce00891fbbe34/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f686f6e657973746f6e652f6c61726176656c2d73656f2f706870)](https://camo.githubusercontent.com/21d9ec171edb00d88631db4817f95cc19532b24de066d914b27ce00891fbbe34/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f686f6e657973746f6e652f6c61726176656c2d73656f2f706870)[![Packagist Dependency Version](https://camo.githubusercontent.com/3d8cc9fa76fddee96dc6e13acb2cee6b8dd13cc7c0ff954d7af3964df17b0e5e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f686f6e657973746f6e652f6c61726176656c2d73656f2f696c6c756d696e617465253246636f6e7472616374733f6c6162656c3d6c61726176656c)](https://camo.githubusercontent.com/3d8cc9fa76fddee96dc6e13acb2cee6b8dd13cc7c0ff954d7af3964df17b0e5e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f686f6e657973746f6e652f6c61726176656c2d73656f2f696c6c756d696e617465253246636f6e7472616374733f6c6162656c3d6c61726176656c)[![Static Badge](https://camo.githubusercontent.com/e61201a0b597af203de29c5edeadfa50ca0eae170c3c27ade60d457258ec2b91/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f686f6e657973746f6e652d666136393030)](https://honeystone.com)

The Honeystone SEO package makes configuring SEO metadata from anywhere within your Laravel application a breeze.

Included are metadata generators for general metadata, X (Formally Twitter) Cards, Open Graph and JSON-LD Schema.

This package was designed with extensibility in mind, so your own custom metadata generators can also be added with ease.

Support us
----------

[](#support-us)

[![Support Us](https://camo.githubusercontent.com/47b52a4f75e2fb7d68935d4d0e6be24ed00d74064147121c1b0658a7faea6da0/68747470733a2f2f686f6e657973746f6e652e636f6d2f696d616765732f6769746875622f737570706f72742d75732e77656270)](https://honeystone.com)

We are committed to delivering high-quality open source packages maintained by the team at Honeystone. If you would like to support our efforts, simply use our packages, recommend them and contribute.

If you need any help with your project, or require any custom development, please [get in touch](https://honeystone.com/contact-us).

Installation
------------

[](#installation)

```
composer require honeystone/laravel-seo
```

Publish the configuration file with:

```
php artisan vendor:publish --tag=honeystone-seo-config
```

Usage
-----

[](#usage)

The package provides a helper function, `seo()`, and some Blade directives, `@metadata` and `@openGraphPrefix`. You can also typehint the `Honeystone\Seo\MetadataDirector` if you prefer to use dependency injection.

Setting metadata is a simple as chaining methods:

```
seo()
    ->title('A fantastic blog post', 'My Awesome Website!')
    ->description('Theres really a lot of great stuff in here...')
    ->images(
        'https://mywebsite.com/images/blog-1/cover-image.webp',
        'https://mywebsite.com/images/blog-1/another-image.webp',
    );
```

Once you've set your metadata, you can render it using:

```
seo()->generate();
```

Alternatively, you can also use the `@metadata` Blade directive.

The rendered result will look something like this:

```
A fantastic blog post - My Awesome Website!

    {
        "@context": "https://schema.org",
        "@type": "WebPage",
        "name": "A fantastic blog post",
        "description": "Theres really a lot of great stuff in here...",
        "image": [
            "https://mywebsite.com/images/blog-1/cover-image.webp",
            "https://mywebsite.com/images/blog-1/another-image.webp"
        ],
        "url": "https://mywebsite.com"
    }

```

### Exporting metadata as arrays

[](#exporting-metadata-as-arrays)

Every generator now implements the `Honeystone\Seo\Contracts\ExportsArrayMetadata` contract, which means the director can provide a structured payload for each generator:

```
$seo = seo()->toArray();            // ['meta' => [...], 'twitter' => [...], ...]
$metaOnly = seo()->toArray('meta'); // limit to a specific generator
```

This is particularly useful when sharing SEO data with Inertia.js:

```
return Inertia::render('Pages/Show', [
    'page' => $page,
    'seo' => seo()->toArray(),
]);
```

On the client you can decide whether to render the tags yourself, hydrate a preview component, or simply inspect the payload for analytics/debugging purposes.

If you are using the bundled `GenerateInertiaMetadata` middleware, the structured payload is automatically shared as `$page.props.seoPayload` (alongside the rendered HTML string that remains available under `$page.props.seo` for legacy integrations).

### Inertia React

[](#inertia-react)

When using Inertia with React, you can render the structured SEO payload directly in the head using the bundled React component at `resources/js/inertia/react/SeoHead.tsx`.

1. Register the React-specific middleware `\Honeystone\Seo\Http\Middleware\GenerateInertiaMetadataReact::class` in your Inertia middleware stack so `$page.props.seoPayload` is shared: ```
    // app/Http/Middleware/HandleInertiaRequests.php
    protected $middleware = [
        // ...
        \Honeystone\Seo\Http\Middleware\GenerateInertiaMetadataReact::class,
    ];
    ```
2. Render the component in your React layout so every page gets the tags: ```
    // resources/js/Layouts/AppLayout.tsx
    import SeoHead from '@/inertia/react/SeoHead';

    export default function AppLayout({ children }) {
        return (

                {children}

        );
    }
    ```

    The component reads `seoPayload` from `usePage()` and outputs meta, Open Graph, Twitter and JSON-LD tags. Pass `forceNoIndex` if you need to override robots for specific responses.

When using this method, Inertia itself will attempt to replace the meta tags for you so you will need to remove the @metadata blade directive from your layouts.

### Default methods

[](#default-methods)

Values provided to default methods will automatically propagate to all configured metadata generators.

The following default methods are available:

```
seo()
    ->locale('en_GB')
    ->title('A fantastic blog post', template: '🔥🔥 {title} 🔥🔥')
    ->description('Theres really a lot of great stuff in here...')
    ->keywords('foo', 'bar', 'baz')
    ->url('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to the current url
    ->canonical('https://mywebsite.com/blog/a-fantastic-blog-post') //by default url and canonical are in sync, see config
    ->canonicalEnabled(true) //enabled by default, see config
    ->images(
        'https://mywebsite.com/images/blog-1/cover-image.webp',
        'https://mywebsite.com/images/blog-1/another-image.webp',
    )
    ->robots('🤖', '🤖', '🤖');
```

The full baseline looks like this:

```
🔥🔥 A fantastic blog post 🔥🔥

    {
        "@context": "https://schema.org",
        "@type": "WebPage",
        "name": "A fantastic blog post",
        "description": "Theres really a lot of great stuff in here...",
        "image": [
            "https://mywebsite.com/images/blog-1/cover-image.webp",
            "https://mywebsite.com/images/blog-1/another-image.webp"
        ],
        "url": "https://mywebsite.com/blog/a-fantastic-blog-post"
    }

```

For your homepage you'll probably want to disable the title template:

```
seo()->title('My Awesome Website!', template: false);
```

### Meta methods

[](#meta-methods)

The meta methods are provided by the `Honeystone\Seo\Generators\MetaGenerator` class.

Here's the full list:

```
seo()
    ->metaTitle('A fantastic blog post')
    ->metaTitleTemplate('🔥🔥 {title} 🔥🔥')
    ->metaDescription('Theres really a lot of great stuff in here...')
    ->metaKeywords('foo', 'bar', 'baz')
    ->metaCanonical('https://mywebsite.com/blog/a-fantastic-blog-post')
    ->metaCanonicalEnabled(true)
    ->metaRobots('🤖', '🤖', '🤖');
```

All of these are provided by the default methods and propagate through to the meta generator.

If you only want to render the meta generator, use `seo()->generate('meta')` or `@metadata('meta')`

### Twitter methods

[](#twitter-methods)

The meta methods are provided by the `Honeystone\Seo\Generators\TwitterGenerator` class.

Here's the full list:

```
seo()
    ->twitterEnabled(true) //enabled by default, see config
    ->twitterSite('@MyWebsite')
    ->twitterCreator('@MyTwitter')
    ->twitterTitle('A fantastic blog post') //defaults to title()
    ->twitterDescription('Theres really a lot of great stuff in here...') //defaults to description()
    ->twitterImage('https://mywebsite.com/images/blog-1/cover-image.webp'); //defaults to the first in images()
```

### Open Graph methods

[](#open-graph-methods)

The meta methods are provided by the `Honeystone\Seo\Generators\TwitterGenerator` class.

Here's the full list:

```
seo()
    ->openGraphEnabled(true) //enabled by default, see config
    ->openGraphSite('My Website')
    ->openGraphType('website') //defaults to website, see config
    ->openGraphTitle('A fantastic blog post') //defaults to title()
    ->openGraphDescription('Theres really a lot of great stuff in here...') //defaults to description()
    ->openGraphImage('https://mywebsite.com/images/blog-1/cover-image.webp')
    ->openGraphImages([
        'https://mywebsite.com/images/blog-1/cover-image.webp',
        'https://mywebsite.com/images/blog-1/another-image.webp',
    ]) //defaults to images()
    ->openGraphUrl('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to url()
    ->openGraphAudio([
        'https://mywebsite.com/music/song1.mp3',
        'https://mywebsite.com/music/song2.mp3',
    ])
    ->openGraphVideo('https://mywebsite.com/films/video1.mp4')
    ->openGraphVideos([
        'https://mywebsite.com/films/video1.mp4',
        'https://mywebsite.com/films/video2.mp4',
    ])
    ->openGraphDeterminer(OpenGraphGenerator::DETERMINER_A)
    ->openGraphLocale('en_GB') //defaults to locale()
    ->openGraphAlternateLocales(['en_US'])
    ->openGraphProperty('custom:property', '💀');
```

You can also use the following non-vertical supported types:

```
use Honeystone\Seo\OpenGraph\ArticleProperties;
use Honeystone\Seo\OpenGraph\BookProperties;
use Honeystone\Seo\OpenGraph\ProfileProperties;

//article
seo()
    ->openGraphType(new ArticleProperties(
        publishedTime: new DateTime('now'),
        modifiedTime: new DateTime('now'),
        expirationTime: null,
        author: new ProfileProperties(
            username: 'PiranhaGeorge',
        ),
        section: 'Foo',
        tag: 'Bar',
    ));

//book
seo()
    ->openGraphType(new BookProperties(
        author: [
            new ProfileProperties(
                firstName: 'Erich',
                lastName: 'Gamma',
            ),
            new ProfileProperties(
                firstName: 'Richard',
                lastName: 'Helm',
            ),
            new ProfileProperties(
                firstName: 'Ralph',
                lastName: 'Johnson',
            ),
            new ProfileProperties(
                firstName: 'John',
                lastName: 'Vlissides',
            ),
        ],
        isbn: '978-0201633610',
        releaseDate: new DateTime('14 March 1995'),
        tag: ['1st', 'GoF'],
    ));

//profile
seo()
    ->openGraphType(new ProfileProperties(
        username: 'PiranhaGeorge'
        firstName: 'George',
        lastName: 'Palmer',
        gender: 'male',
    ));
```

You can provide more data for images, audio and videos using their respective properties classes:

```
    use Honeystone\Seo\OpenGraph\AudioProperties;
    use Honeystone\Seo\OpenGraph\ImageProperties;
    use Honeystone\Seo\OpenGraph\VideoProperties;

    seo()->openGraphAudio(new AudioProperties(
        url: 'http://foo.bar/song.mp3',
        secureUrl: 'https://foo.bar/song.mp3',
        type: 'audio/mpeg',
    ));

    seo()->openGraphImage(new ImageProperties(
        url: 'http://foo.bar/img.png',
        alt: 'Foo',
        width: '800',
        height: '450',
        secureUrl: 'https://foo.bar/img.png',
        type: 'image/png',
    ));

    seo()->openGraphVideo(new VideoProperties(
        url: 'http://foo.bar/movie.mp4',
        alt: 'Foo',
        width: '1920',
        height: '1080',
        secureUrl: 'https://foo.bar/movie.mp4',
        type: 'video/mp4',
    ));
```

Here's an example using `ArticleProperties` and `ImageProperties`:

```

```

To set the prefix, you can use the `@openGraphPrefix` Blade directive or `seo()->openGraphPrefix()` like so:

```

    ...

```

### JSON-LD methods

[](#json-ld-methods)

The meta methods are provided by the `Honeystone\Seo\Generators\JsonLdGenerator` class.

Here's the full list:

```
seo()
    ->jsonLdEnabled(true) //enabled by default, see config
    ->jsonLdType('WebPage') //defaults to WebPage, see config
    ->jsonLdName('A fantastic blog post') //defaults to title()
    ->jsonLdDescription('Theres really a lot of great stuff in here...') //defaults to description()
    ->jsonLdImage('https://mywebsite.com/images/blog-1/cover-image.webp')
    ->jsonLdImages([
        'https://mywebsite.com/images/blog-1/cover-image.webp',
        'https://mywebsite.com/images/blog-1/another-image.webp',
    ]) //defaults to images()
    ->jsonLdUrl('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to url()
    ->jsonLdNonce('some-value') //sets a nonce value for your content security policy
    ->jsonLdProperty('alternateName', 'Foo');
```

And the output:

```

    {
        "@context": "https://schema.org",
        "@type": "WebPage",
        "name": "A fantastic blog post",
        "description": "Theres really a lot of great stuff in here...",
        "image": [
            "https://mywebsite.com/images/blog-1/cover-image.webp",
            "https://mywebsite.com/images/blog-1/another-image.webp"
        ],
        "url": "https://mywebsite.com/blog/a-fantastic-blog-post",
        "alternateName": "Foo"
    }

```

But Wait, There's More!

Rather than reinventing the wheel, this package has support for the incredible [spatie/schema-org](https://github.com/spatie/schema-org) package. You can use the `jsonLdImport()` method to import an exising schema, or build your schema using the fluent interface.

```
    //graph
    seo()->jsonLdGraph()
        ->organization('honeystone')
            ->name('Honeystone')
            ->legalName('Honeystone Consulting Ltd.');

    //or a MultiTypedEntity
    seo()->jsonLdMulti()
        ->organization('honeystone')
            ->name('Honeystone')
            ->legalName('Honeystone Consulting Ltd.');
```

Just don't forget to install the `spatie/schema-org` package to use this functionality.

#### Expectations / Check Ins

[](#expectations--check-ins)

It's highly likely you'll be building your graph from many locations around your application, e.g. middleware, controllers, view composers, view components, etc.

This is where expectations come in. Simply specify your expectations, and then ensure the other parts of your application check in. If something failed to check in, an exception will be thrown. Conversely, if something unexpected checked in, an exception will also be thrown.

```
//perhaps in a controller
seo()
    ->title('Something awesome')
    ->jsonLdExpect('featured-tags', 'gallery', 'contact');

//maybe in a view composer or component
seo()
    ->jsonLdCheckIn('gallery')
    ->jsonLdGraph()
        ->imageGallery()
            ->image([
                ...
            ]);
```

You'll be warned immediately if `'featured-tags'` or `'contact'` fail to check in.

This feature is entirely optional. Just don't set any expectations, or check in, and no exceptions will be thrown.

### Model integration

[](#model-integration)

This package doesn't include any specific functionality for integrating models. Ultimately, you'll always need to map your model attributes to this package. For example, if your model has a `meta_description` attribute, you will need to map it to `description`, otherwise this package would not know to consume it.

With this in mind, we have a simple pattern that should get you what you need.

Start by adding a new method to your model, and set your metadata using the model's attributes within:

```
use Honeystone\Seo\MetadataDirector;
use Illuminate\Database\Eloquent\Model;

class Page extends Model
{
    public function seo(): MetadataDirector
    {
        return seo()
            ->title($this->meta_title)
            ->description($this->meta_description)
            ->jsonLdExpect('featured-items');
    }
}
```

Then in your controller, just call the method and chain any additional metadata:

```
use Illuminate\Contracts\View\View;

class PageController
{
    public function __invoke(Page $page): View
    {
        $page->seo()
            ->jsonLdCheckIn('featured-items')
            ->jsonLdGraph()
                ->itemList()
                    ->name('Featured items')
                    ->itemListElement([
                        //...
                    ]);
    }
}
```

### Custom generators

[](#custom-generators)

To create a custom generator, simply implement the `Honeystone\Seo\Contracts\MetadataGenerator` contract and add it to your config file in the generators section. You can specify any configuration for your generator here too.

### Removing the source comments

[](#removing-the-source-comments)

The package includes comments in the generated HTML. If you would like to remove them, you can publish the views and remove the comments from the Blade files:

```
php artisan vendor:publish --tag=honeystone-seo-views
```

Publishing the package views is generally not recommended as these files will not be updated when the package is updated. If it is absolutely necessary to publish the views, we recommend only publishing the files you need to modify.

### Configuration

[](#configuration)

Here's the full config file:

```
