laravel-seo maintained by jegex
Laravel SEO Package
A comprehensive SEO package for Laravel inspired by Rank Math SEO (WordPress). Features include meta tags, Open Graph, Twitter Cards, template variables, sitemap generation, redirect management, and 404 tracking.
Note: This is a core SEO package - no admin dashboard included. For a Filament admin dashboard, check out the separate
filament-seopackage.
Support us
We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.
Installation
You can install the package via composer:
composer require jegex/laravel-seo
You can publish and run the migrations with:
php artisan vendor:publish --tag="laravel-seo-migrations"
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="laravel-seo-config"
Optionally, you can publish the views using
php artisan vendor:publish --tag="laravel-seo-views"
Quick Start
1. Add Trait to Your Model
use Jegex\LaravelSeo\Traits\HasSeo;
class Post extends Model
{
use HasSeo;
}
2. Use in Your Blade Layout
<!DOCTYPE html>
<html>
<head>
{{ seo()->render() }}
</head>
<body>
@yield('content')
</body>
</html>
3. Configure Templates (config/seo.php)
return [
'site_name' => 'My Blog',
'site_description' => 'A blog about Laravel',
'separator' => ' - ',
'templates' => [
'post' => [
'title' => '%title% %sep% %sitename%',
'description' => '%excerpt%',
],
],
'webmaster_verification' => [
'google' => 'your-google-verification-code',
'bing' => 'your-bing-verification-code',
],
];
Features
Template Variables (Rank Math Style)
Use these variables in your templates:
| Variable | Description |
|---|---|
%title% |
Model title |
%sitename% |
Website name |
%sitedesc% |
Website description |
%sep% |
Separator (default: -) |
%currentdate% |
Current date |
%currentyear% |
Current year |
%author% |
Author name |
%excerpt% |
Content excerpt (160 chars) |
%categories% |
Categories list |
%tags% |
Tags list |
Helper Functions
// Set SEO data
seo('title', 'My Page Title');
seo('description', 'Page description');
seo('og:image', '/path/to/image.jpg');
// Or use methods
seo()->setTitle('My Title')->setDescription('My Description');
// Parse templates manually
parse_seo_template('%title% %sep% %sitename%', ['title' => 'Hello']);
Blade Components
<x-seo::meta-tags />
<x-seo::json-ld :schema="['@type' => 'Article', 'headline' => $title]" />
<x-seo::breadcrumbs :items="[['name' => 'Home', 'url' => '/'], ['name' => $category]]" />
Webmaster Verification
Add verification codes in config/seo.php:
'webmaster_verification' => [
'google' => 'abc123...',
'bing' => 'xyz789...',
'pinterest' => 'pinterest-code',
'yandex' => 'yandex-code',
],
Redirect Management
use Jegex\LaravelSeo\Models\Redirect;
// Create a 301 redirect
Redirect::create([
'from_url' => '/old-page',
'to_url' => '/new-page',
'type' => 301,
]);
// Create a regex redirect
Redirect::create([
'from_url' => '#/blog/(.*)#',
'to_url' => '/articles/$1',
'type' => 301,
'is_regex' => true,
]);
// 410 Gone
Redirect::create([
'from_url' => '/deleted-page',
'type' => 410,
]);
Middleware
Add to your app/Http/Kernel.php:
protected $middleware = [
// ...
\Jegex\LaravelSeo\Http\Middleware\RedirectMiddleware::class,
\Jegex\LaravelSeo\Http\Middleware\NotFoundTrackerMiddleware::class,
];
SEO Analysis
CLI Command
# Analyze all SEO entries
php artisan seo:analyze
# Analyze specific model
php artisan seo:analyze --model="App\Models\Post"
# Calculate and save scores
php artisan seo:analyze --calculate-scores
Model Analysis
$entry = $post->seoEntry;
// Get full analysis
$analysis = $entry->analyze();
// [
// 'score' => 85,
// 'alerts' => ['Title is slightly long...'],
// 'checks' => [...],
// 'details' => [...]
// ]
// Get score only
$entry->calculateScore();
echo $entry->seo_score; // 85
// Get score label
echo $entry->getScoreLabel(); // 'Good', 'Needs Improvement', or 'Poor'
Programmatic Analysis
use Jegex\LaravelSeo\Services\AnalyzerService;
$analyzer = app(AnalyzerService::class);
$analysis = $analyzer->analyze($content, [
'title' => 'My Title',
'description' => 'My Description',
'focus_keyword' => 'laravel seo',
]);
echo $analysis['score']; // 0-100
echo $analysis['alerts'][0]; // First alert message
Breadcrumbs
Via Service
// Manual breadcrumbs
seo()->breadcrumbs()
->add('Home', '/')
->add('Blog', '/blog')
->add($post->title, $post->url());
// Auto from route
seo()->breadcrumbs()->fromRoute();
// Render
{{ seo()->breadcrumbs()->renderHtml() }}
{{ seo()->breadcrumbs()->renderSchema() }}
Via Blade Component
<x-seo::breadcrumbs :items="[
['name' => 'Home', 'url' => '/'],
['name' => 'Blog', 'url' => '/blog'],
['name' => $post->title]
]" />
JSON-LD Structured Data
Available Schema Types
article- Article/BlogPostingwebsite- WebSite with SearchActionorganization- Organization with contact infobreadcrumbs- BreadcrumbList
Creating Schema
// Via SEO service (auto-rendered)
seo()->addSchema('article')
->headline('My Article')
->author('John Doe')
->datePublished(now()->toIso8601String())
->image('https://example.com/image.jpg');
// Via Schema service
seo()->schema()
->website()
->name('My Site')
->url('/')
->potentialActionSearch('/search?q={search_term_string}');
// Organization schema
seo()->schema()
->organization()
->name('My Company')
->url('https://example.com')
->contactPoint('+1-234-567-8900', 'customer service');
Rendering
{{-- In your layout, schemas auto-render with meta tags --}}
{{ seo()->render() }}
{{-- Or render schemas only --}}
{{ seo()->schema()->render() }}
{{-- Manual schema --}}
<x-seo::json-ld :schema="['@type' => 'Article', 'headline' => $title]" />
Complete Example
Controller
class PostController extends Controller
{
public function show(Post $post)
{
// Automatically uses HasSeo trait for defaults
seo()->for($post);
// Or manually override
seo()->setTitle($post->title . ' | Custom Suffix');
return view('posts.show', compact('post'));
}
}
View (posts/show.blade.php)
@extends('layouts.app')
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<div>{{ $post->content }}</div>
</article>
@endsection
Layout (layouts/app.blade.php)
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{-- Render all SEO meta tags --}}
{{ seo()->render() }}
@stack('styles')
</head>
<body>
@yield('content')
@stack('scripts')
</body>
</html>
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.