spatie/schema-org
Fluent PHP builder for the full Schema.org vocabulary. Create Schema.org types and properties via chainable methods and output valid JSON-LD/ld+json scripts for SEO. Auto-generated from Schema.org standards for complete coverage.
Installation:
composer require spatie/schema-org
Add to composer.json if using Laravel’s autoloader.
First Use Case:
Generate a simple LocalBusiness schema for SEO:
use Spatie\SchemaOrg\Schema;
$business = Schema::localBusiness()
->name('Acme Corp')
->email('contact@acme.com')
->telephone('+123456789');
echo $business->toScript();
Outputs:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Acme Corp",
"email": "contact@acme.com",
"telephone": "+123456789"
}
</script>
Where to Look First:
src/ for type definitions (auto-generated from Schema.org).Fluent Builder Pattern: Chain methods for readability:
$article = Schema::article()
->headline('Laravel 11 Released')
->datePublished('2024-01-01')
->author($graph->person('author1')->name('Jane Doe'));
Graph for Complex Relationships:
Use Graph to manage interconnected schemas (e.g., Product + Offer + Organization):
$graph = new Graph();
$graph->product()
->name('Premium Widget')
->brand($graph->organization()->name('Acme Inc'))
->offers(Schema::offer()->price(99.99)->currency('USD'));
echo $graph->toScript(); // Renders all linked schemas
Conditional Properties:
Avoid breaking chains with if():
$schema = Schema::localBusiness()
->name('Store')
->if($hasEmail, fn($s) => $s->email('info@example.com'));
Multi-Typed Entities (MTE):
For hybrid schemas (e.g., HotelRoom + Product):
$mte = new MultiTypedEntity();
$mte->hotelRoom()->name('Suite A');
$mte->product()->aggregateRating(Schema::aggregateRating()->ratingValue(4.5));
Dynamic Properties:
Use setProperty() for non-standard Schema.org fields (e.g., custom extensions):
$schema->setProperty('customField', 'value');
Service Provider: Register a singleton for global schema access:
// app/Providers/SchemaServiceProvider.php
public function register()
{
$this->app->singleton('schema', fn() => new Schema());
}
Blade Directives:
Create a @schema directive for views:
// app/Providers/BladeServiceProvider.php
Blade::directive('schema', function ($expression) {
return "<?php echo app('schema')->{$expression}->toScript(); ?>";
});
Usage:
@schema("localBusiness()->name('Store')")
Model Observers:
Auto-generate schemas on model events (e.g., saved):
// app/Observers/ProductObserver.php
public function saved(Product $product)
{
$schema = Schema::product()
->name($product->name)
->sku($product->sku);
$product->schema = $schema->toScript();
$product->save();
}
SEO Meta Tags:
Inject schemas into <head> via a view composer:
// app/Providers/AppServiceProvider.php
public function boot()
{
View::composer('*', function ($view) {
$view->with('schema', Schema::organization()->name(config('app.name')));
});
}
HTML Injection in toScript():
</script> break the generated script tag.toArray() or json_encode() for dynamic content, or escape manually:
$schema->description(htmlspecialchars($userInput));
</> to \u003C/\u003E.Circular References:
author in article) may cause infinite loops in toArray().Graph or manually set @id:
$author = Schema::person()->name('John');
$author->setId('https://example.com/authors/john');
$article->author($author);
Reserved Keywords:
Float type is unavailable (PHP reserved word).float strings or number type.Missing Types:
Physician type is excluded (health extension).MedicalEntity or extend the package.Array vs. Single Values:
sameAs accept arrays but may fail if passed as a string.->sameAs(['https://url1.com', 'https://url2.com']);
Validate with Google’s Tool: Use Rich Results Test to catch errors early.
Inspect Raw Output:
dd($schema->toArray()); // Debug structure
Check for Deprecated Methods:
identifier → @id (since v2.6.0).Type-Safe Enumerations:
Use constants for predefined values (e.g., BookFormatType::Hardcover).
Reuse Instances:
Cache frequently used schemas (e.g., Organization for all products):
$company = Schema::organization()->name('Acme');
// Reuse $company across multiple products
Lazy Loading:
Defer schema generation until needed (e.g., in a getSchema() method).
Minimize Graph Size:
Use hide() to exclude helper schemas:
$graph->hide(Organization::class); // Exclude from output
Custom Types:
Extend SchemaOrg\Type for domain-specific schemas:
class CustomType extends \Spatie\SchemaOrg\Type
{
public function customMethod($value)
{
$this->setProperty('customField', $value);
return $this;
}
}
Override Property Handling:
Extend SchemaOrg\Property to validate or transform values:
class CustomProperty extends \Spatie\SchemaOrg\Property
{
public function __construct($name, $value)
{
parent::__construct($name, strtoupper($value));
}
}
Modify Generation:
Override toArray() or toScript() in a custom class:
class CustomSchema extends \Spatie\SchemaOrg\Schema
{
public function toScript()
{
$array = $this->toArray();
$array['@context'] = 'https://custom.org';
return '<script type="application/ld+json">' . json_encode($array) . '</script>';
}
}
Integrate with Laravel Scout: Use schemas for search engine optimization:
$product->searchableAs('product')->addSelectableAttributes(['schema']);
How can I help you explore Laravel packages today?