cmelda/json-ld

JSON-LD builder for standalone entities and graphs with automatic entity references

Maintainers

Package info

gitlab.com/cmelda/json-ld

Issues

pkg:composer/cmelda/json-ld

Fund package maintenance!

Ko-Fi

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 0

2.1 2026-06-05 13:21 UTC

This package is auto-updated.

Last update: 2026-06-05 11:22:42 UTC


README

Latest Stable Version Total Downloads License PHP Version Require

PHP library for standalone JSON-LD entities and JSON-LD documents with an @graph. Entities with an @id are linked by reference and automatically added to the document graph once.

Installation

composer require cmelda/json-ld

Standalone entity

use Cmelda\JsonLd\Types\Person;

echo Person::make('https://example.com', 'person')
	->setName('George')
	->toScript();

Graph document

use Cmelda\JsonLd\Core\JsonLdDocument;
use Cmelda\JsonLd\Types\Article;
use Cmelda\JsonLd\Types\Person;

$person = Person::make('https://example.com', 'person')->setName('George');
$article = Article::make('https://example.com/article')
	->setHeadline('Sample article')
	->addAuthor($person)
	->addEditor($person);

echo JsonLdDocument::make()->add($article)->toScript();

The article contains two {"@id":"https://example.com#person"} references, but the full person is present in @graph only once.

Shared organization

use Cmelda\JsonLd\Core\JsonLdDocument;
use Cmelda\JsonLd\Types\Article;
use Cmelda\JsonLd\Types\Organization;
use Cmelda\JsonLd\Types\Person;

$organization = Organization::make('https://example.com', 'organization')
	->setName('Company');
$person = Person::make('https://example.com', 'person')
	->setName('George')
	->setWorksFor($organization);
$article = Article::make('https://example.com/article')
	->addAuthor($person)
	->setPublisher($organization);

echo JsonLdDocument::make()->add($article)->toScript();

Every parent stores an @id reference and the shared organization appears in @graph once.

Inline and linked images

use Cmelda\JsonLd\Types\ImageObject;

$inline = ImageObject::make()->setUrl('https://example.com/image.jpg');
$linked = ImageObject::make('https://example.com/image.jpg')
	->setUrl('https://example.com/image.jpg');

An image without an @id is embedded inline. An image with an @id is linked by reference and added to the graph.

Standalone rendering is rejected when an entity has linked dependencies. Render the document in that case. Entities remain reusable after they have been added to a JsonLdDocument.

FAQ page

use Cmelda\JsonLd\Types\Answer;
use Cmelda\JsonLd\Types\FAQPage;
use Cmelda\JsonLd\Types\Question;

$faqPage = FAQPage::make()
	->addMainEntity(
		Question::make()
			->setName('How long is the warranty?')
			->setAcceptedAnswer(Answer::make()->setText('The warranty is two years.')),
	);

echo $faqPage->toScript();

The PHP helper AdditionalProperty renders the Schema.org type PropertyValue. Use VideoObject for Schema.org video metadata.

Practical metadata helpers

The library includes small helpers for common structured data metadata without trying to wrap the whole Schema.org vocabulary:

  • sameAs: addSameAs() on any Thing type
  • taxonomy: DefinedTerm and DefinedTermSet
  • audience: Audience and PeopleAudience
  • areas served: setAreaServed() on Organization, LocalBusiness, Product, Offer and ContactPoint
  • product relations: addIsAccessoryOrSparePartFor() and addIsConsumableFor()
  • commerce shortcuts: OfferShippingDetails::forCountry() and MerchantReturnPolicy::forCountry()
  • content about a thing: addSubjectOf() accepts CreativeWork or Event and automatically adds the inverse about reference when the parent has an @id

Schema.org enumerations

For common Schema.org enum values, prefer library enums instead of raw strings:

use Cmelda\JsonLd\Enums\ItemAvailability;
use Cmelda\JsonLd\Enums\ItemCondition;
use Cmelda\JsonLd\Types\Offer;
use Cmelda\JsonLd\Types\Product;

$product = Product::make()
	->setItemCondition(ItemCondition::New);

$offer = Offer::make()
	->setAvailability(ItemAvailability::InStock);

The setters still accept a string URL for new Schema.org values that are not yet present in the library.

Offer price specification

Use priceSpecification when you need detailed offer pricing, unit pricing or compound price components:

use Cmelda\JsonLd\Types\Offer;
use Cmelda\JsonLd\Types\UnitPriceSpecification;

$offer = Offer::make()
	->addPriceSpecification(
		UnitPriceSpecification::make()
			->setPrice('49.90')
			->setPriceCurrency('USD')
			->setValueAddedTaxIncluded(true),
	);

Video metadata

VideoObject maps common CMS fields to Schema.org VideoObject properties and extends MediaObject, which extends CreativeWork, which extends Thing. Attach videos to products, offers or other things through addSubjectOf():

use Cmelda\JsonLd\Types\Product;
use Cmelda\JsonLd\Types\VideoObject;

$product = Product::fromUrl('https://example.com/products/widget')
	->setName('Widget')
	->addSubjectOf(
		VideoObject::make('https://example.com/products/widget', 'video')
			->setTitle('Widget overview', 'en-US')
			->setThumbnailUrl('https://example.com/video.jpg')
			->setUploadDate('2026-02-04T10:00:00+01:00'),
	);

Product.subjectOf contains a reference to the video and the VideoObject contains about with a reference back to the product.

Common video fields:

  • localized video title fields: use setTitle($title, $language)
  • localized video descriptions: use setDescription($description) with setInLanguage($language)
  • thumbnail_url: use setThumbnailUrl()
  • upload_date: use setUploadDate()
  • duration_seconds: use setDurationSeconds(), rendered as ISO 8601 duration
  • content_url: use setContentUrl()
  • embed_url: use setEmbedUrl()
  • publisher: use setPublisher()
  • author: use addAuthor()
  • media format and dimensions: use setEncodingFormat(), setWidth() and setHeight()
  • transcript/captions: use setTranscript() and setCaption()
  • license/copyright: use setLicense(), setCopyrightHolder() and setCopyrightYear()
  • video_source: use setVideoSource(), rendered as additionalProperty
  • active: use setActive(), rendered as additionalProperty
  • language: use setInLanguage()

Factory fromUrl()

fromUrl() is an explicit way to create an @id from a URL. setUrl() only sets the url property and never changes @id.

use Cmelda\JsonLd\Types\Person;

$person = Person::fromUrl('https://example.com/authors/alex')
	->setName('Alex');

Use make($url, $fragment) when you already have a canonical URL and want a specific fragment @id without manually concatenating #fragment:

use Cmelda\JsonLd\Types\LocalBusiness;

$store = LocalBusiness::make('https://example.com/store', 'store-ostrava')
	->setName('Ostrava store');

Person, Organization, Product and LocalBusiness use a type-specific URL fragment. Article, BlogPosting and TechArticle use their URL directly as @id. A review uses Review::fromUrlAndId() because one product page can contain multiple reviews.

Article types

Use Article for generic article content, BlogPosting for blog posts and TechArticle for technical documentation or tutorials. BlogPosting follows the Schema.org hierarchy through SocialMediaPosting and then Article:

use Cmelda\JsonLd\Types\BlogPosting;
use Cmelda\JsonLd\Types\TechArticle;

$post = BlogPosting::fromUrl('https://example.com/blog/widget-guide')
	->setHeadline('Widget guide')
	->setBlogSection('Guides');

$technical = TechArticle::fromUrl('https://example.com/docs/widget-api')
	->setHeadline('Widget API')
	->setProficiencyLevel('Beginner')
	->addProgrammingLanguage('PHP');

Reviews

For a regular customer review, keep the customer, review and rating inline without an @id:

use Cmelda\JsonLd\Types\Product;
use Cmelda\JsonLd\Types\Rating;
use Cmelda\JsonLd\Types\Review;

$review = Review::make()
	->setAuthor('Jamie K.')
	->setReviewBody('The widget works perfectly.')
	->setReviewRating(Rating::make()->setRatingValue(5)->setBestRating(5));

$product = Product::fromUrl('https://example.com/products/nimbus-widget')
	->setName('Nimbus Widget')
	->addReview($review);

For an expert review, use Person::fromUrl() for the author's profile and Review::fromUrlAndId() for the stable review identifier. The product contains a review reference, the review contains an author reference and both entities are added to @graph.

Validation fixture

The JSON files in tests/Support/Data/ contain complete examples suitable for manual validation with online JSON-LD tools. The unit test suite compares the fixtures with documents generated by the library.

More extensive PHP examples with their JSON outputs are available in docs/.

Migrating from v1

Version 2 uses consistent addX() methods for properties with multiple values:

$product
	->addImage('https://example.com/image.jpg')
	->addReview($review);

$offer->addShippingDetails($shippingDetails);
$organization->addContactPoint($contactPoint);

Product::setBrand() accepts Brand|string and Product::setAggregateRating() accepts AggregateRating. Entities remain reusable and their @id can be changed after adding them to a document.

License

Distributed under the MIT License. See LICENSE.txt for more information.

Donation

ko-fi