artesaos/seotools
SEOTools adds SEO helpers for Laravel and Lumen: quickly set page titles, meta tags, Open Graph, Twitter Cards, and JSON-LD structured data via a simple, friendly API. Supports modern Laravel versions with package discovery.
Installation:
composer require artesaos/seotools
Laravel 5.5+ automatically registers the package via Package Discovery. For older versions, add the provider to config/app.php:
'providers' => [
Artesaos\SEOTools\Providers\SEOToolsServiceProvider::class,
],
Publish Config (optional but recommended):
php artisan vendor:publish --provider="Artesaos\SEOTools\Providers\SEOToolsServiceProvider"
This generates config/seotools.php for customization.
First Use Case: In a controller, set basic meta tags:
use Artesaos\SEOTools\Facades\SEOMeta;
use Artesaos\SEOTools\Facades\OpenGraph;
public function index()
{
SEOMeta::setTitle('My Page Title');
SEOMeta::setDescription('A concise description for SEO.');
OpenGraph::setUrl(url()->current());
OpenGraph::setTitle('My Page Title');
OpenGraph::setImage(url('images/og-image.jpg'));
}
Blade Integration:
Add this to your layout file (e.g., resources/views/layouts/app.blade.php):
<head>
{!! SEO::generate() !!}
<!-- Other head elements -->
</head>
Use facades or the SEOTools facade to centralize SEO logic in controllers:
public function show(Post $post)
{
SEOTools::setTitle($post->title)
->setDescription($post->excerpt)
->setCanonical(url()->current())
->opengraph()
->setUrl(url()->current())
->setImage($post->featured_image)
->addProperty('article:published_time', $post->published_at->toW3CString())
->twitter()
->setSite('@myhandle')
->setCardType('summary_large_image');
}
Leverage collections or loops to generate dynamic tags:
public function category(Category $category)
{
SEOMeta::setTitle("Category: {$category->name}");
SEOMeta::addKeyword($category->tags->pluck('slug')->toArray());
OpenGraph::setTitle("Category: {$category->name}");
OpenGraph::addImage($category->posts->take(3)->pluck('thumbnail'));
}
Use JsonLd or JsonLdMulti for schema markup (e.g., articles, products):
// Single JSON-LD block
JsonLd::setTitle($product->name)
->setDescription($product->description)
->setType('Product')
->addProperty('price', $product->price)
->addProperty('availability', $product->stock > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock')
->addImage($product->images);
// Multiple JSON-LD blocks (e.g., for breadcrumbs + product)
JsonLdMulti::setTitle('Breadcrumb')
->setType('Breadcrumb')
->addProperty('itemListElement', [
['position' => 1, 'name' => 'Home', 'item' => url('/')],
['position' => 2, 'name' => $category->name, 'item' => url("/category/{$category->slug}")]
]);
if (!JsonLdMulti::isEmpty()) {
JsonLdMulti::newJsonLd(); // Start a new block
JsonLd::setType('Product')->setTitle($product->name);
}
Create a service class to encapsulate SEO logic:
// app/Services/SEOService.php
class SEOService
{
public function configureBlogPost(Post $post)
{
SEOMeta::setTitle($post->title)
->setDescription($post->excerpt)
->addMeta('article:published_time', $post->published_at->toW3CString(), 'property');
OpenGraph::setTitle($post->title)
->setDescription($post->excerpt)
->setUrl(url()->current())
->addImage($post->featured_image)
->addProperty('article:section', $post->category->slug);
JsonLd::setType('BlogPosting')
->setTitle($post->title)
->addProperty('headline', $post->title)
->addProperty('description', $post->excerpt)
->addProperty('datePublished', $post->published_at->toISOString());
}
}
Usage:
$this->seoService->configureBlogPost($post);
Apply SEO defaults via middleware (e.g., app/Http/Middleware/SetSEODefaults.php):
public function handle(Request $request, Closure $next)
{
SEOMeta::setDefaultTitle(config('app.name'));
SEOMeta::setDefaultDescription(config('seotools.meta.defaults.description'));
OpenGraph::setDefaultImage(config('seotools.opengraph.defaults.image'));
return $next($request);
}
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\SetSEODefaults::class,
],
];
Use Laravel’s HTTP tests to verify SEO tags:
public function test_homepage_seo()
{
$response = $this->get('/');
$response->assertSee('<title>My Page Title</title>');
$response->assertSee('property="og:title" content="My Page Title"');
$response->assertJsonStructure([
'script' => [
['type' => 'application/ld+json'],
],
]);
}
Facades in Lumen:
$seotools = app('seotools');
$seotools->setTitle('Lumen Page');
Overwriting Defaults:
config/seotools.php are not merged with dynamic values. Explicitly set values will override defaults, but unset values will fall back to defaults. To disable defaults entirely, set them to false or null:
'meta' => [
'defaults' => false, // Disables all default meta tags
],
JSON-LD Validation:
name for Product schema).DateTime field).@type declarations in JsonLdMulti.OpenGraph/Twitter Card Validation:
og:url).og:title and Twitter card title.Canonical URLs:
SEOMeta::setCanonical(url()->current());
url()->current() or route()->current() to avoid hardcoding.Performance Impact:
JsonLdMulti blocks or large OpenGraph images can slow down page rendering. Audit with Lighthouse or WebPageTest.Blade Cache Conflicts:
php artisan view:cache), clear the cache after changing SEO configurations:
php artisan view:clear
Inspect Generated HTML:
Ctrl+U) to verify tags are rendered correctly. Look for:
<meta> tags in the <head>.<script type="application/ld+json"> for JSON-LD.property="og:image").Log SEO Output:
file_put_contents(
storage_path('logs/seo-debug.html'),
SEOTools::generate()
);
Validate Schema Markup:
Check for Deprecated Methods:
SEOMeta::addMeta('keywords', [...]).How can I help you explore Laravel packages today?