spatie/schema-org
Fluent PHP builder for Schema.org: generate any type and property from the full core vocabulary and output valid JSON-LD (ld+json) script tags. Generated from the official Schema.org JSON-LD, with documented classes and methods.
Installation:
composer require spatie/schema-org
No additional configuration is required—just autoload the package.
First Use Case:
Generate a basic Person type with minimal properties:
use Spatie\SchemaOrg\Schema;
$person = Schema::person()
->name('John Doe')
->jobTitle('Developer')
->sameAs('https://example.com/john-doe');
echo $person->toHtml();
Outputs:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "John Doe",
"jobTitle": "Developer",
"sameAs": "https://example.com/john-doe"
}
</script>
Where to Look First:
vendor/spatie/schema-org/src/Types/ for all available Schema.org types (e.g., Article, Product, LocalBusiness).Fluent Chaining for Types:
Build complex nested structures (e.g., BreadcrumbList with ListItem):
$breadcrumbs = Schema::breadcrumbList()
->addItem(
Schema::listItem()
->position(1)
->name('Home')
->item(Schema::thing()->name('Homepage'))
)
->addItem(
Schema::listItem()
->position(2)
->name('Products')
->item(Schema::thing()->name('Products Page'))
);
Dynamic Property Assignment:
Use addProperty() for unsupported properties or future-proofing:
$product = Schema::product()
->name('Laptop')
->addProperty('mpn', '123456789'); // Manufacturer Part Number (custom)
Reusable Components: Extract common schemas into helper methods or services:
function generateOrganizationSchema(string $name, string $url): Schema\Organization
{
return Schema::organization()
->name($name)
->url($url)
->logo('https://example.com/logo.png');
}
SEO Optimization:
Combine multiple schemas in a single page (e.g., WebPage + BreadcrumbList):
$pageSchema = Schema::webPage()
->name('Blog Post')
->addProperty('@type', ['BlogPost', 'WebPage'])
->addProperty('mainEntityOfPage', Schema::webPage()->url('/blog/post'))
->addProperty('breadcrumb', $breadcrumbs->toArray());
API Integration: Serve schemas via API responses:
return response()->json([
'data' => $product->toArray(),
'schema' => $product->toJson(),
]);
Blade Directives: Create a Blade directive for easy schema injection:
Blade::directive('schema', function ($type) {
return "<?php echo \\Spatie\\SchemaOrg\\Schema::$type()->toHtml(); ?>";
});
Usage:
@schema('webPage')
Caching: Cache generated schemas if they’re static (e.g., business hours):
$cacheKey = 'business_hours_schema';
$hours = Cache::remember($cacheKey, now()->addHours(1), function () {
return Schema::openingHoursSpecification()
->opens('09:00')
->closes('17:00');
});
Validation:
Validate properties against Schema.org constraints (e.g., URL format):
use Spatie\SchemaOrg\Exceptions\InvalidPropertyValue;
try {
$schema = Schema::product()->addProperty('url', 'invalid-url');
} catch (InvalidPropertyValue $e) {
// Handle invalid URL
}
Property Overrides:
addProperty() to append without replacing:
// Wrong: Overrides 'name' entirely
Schema::person()->name('John')->name('Jane'); // 'name' = 'Jane'
// Correct: Appends to existing properties
Schema::person()->name('John')->addProperty('name', 'Jane'); // 'name' = ['John', 'Jane']
Nested Schema Limitations:
Offer inside Product) must be fully built before assignment:
// Wrong: Incomplete nested schema
$product = Schema::product()->offers(Schema::offer()->price('100'));
// Correct: Fully built nested schema
$offer = Schema::offer()->price('100')->url('/offer');
$product = Schema::product()->offers($offer);
Context Management:
@context is hardcoded to https://schema.org. For custom contexts, use addProperty('@context', 'custom-context'), but validate compatibility with Schema.org validators.Type Conflicts:
Person and Organization as author of an Article). Schema.org validators may flag this as invalid.Dynamic Property Validation:
addProperty()) bypass built-in validation. Use Schema::validate() to enforce rules:
Schema::validate($schema, [
'url' => 'required|url',
'price' => 'numeric',
]);
Schema Validation:
Use Google’s Rich Results Test or Schema Markup Validator to debug issues. The package includes a toJson() method for easy testing:
echo $schema->toJson(JSON_PRETTY_PRINT);
Type Autocompletion:
IDEs (PHPStorm, VSCode) may not autocomplete dynamically added properties. Cast to array first:
$schema->addProperty('customField', 'value');
$array = $schema->toArray(); // IDE now sees all properties
Deprecation Warnings: Monitor Schema.org’s changelog for deprecated properties/types. The package updates annually but may lag behind minor Schema.org changes.
Custom Types: Extend the package by creating a custom type class:
namespace App\SchemaOrg;
use Spatie\SchemaOrg\Contracts\Schema;
use Spatie\SchemaOrg\SchemaOrg;
class CustomType extends SchemaOrg
{
protected $type = 'CustomType';
public function customMethod(string $value): self
{
$this->properties['customProperty'] = $value;
return $this;
}
}
Register it in Schema::macro():
Schema::macro('customType', function () {
return new CustomType();
});
Property Macros: Add reusable property chains:
Schema::macro('addAuthor', function (string $name, string $url) {
return $this->addProperty('author', Schema::person()
->name($name)
->url($url)
->toArray());
});
Event Listeners: Hook into schema generation (e.g., log all schemas):
Schema::macro('toJson', function () {
$json = parent::toJson();
Log::info('Generated schema:', ['schema' => $json]);
return $json;
});
Testing:
Use Schema::fake() to mock schemas in tests:
Schema::fake([
'person' => fn() => Schema::person()->name('Test User'),
]);
$schema = Schema::person(); // Returns mocked schema
How can I help you explore Laravel packages today?