torann/json-ld
torann/json-ld is a Laravel package for generating JSON-LD structured data. It provides a fluent builder for schema.org markup, supports multiple schema types, and makes adding SEO-friendly rich results to your application simple.
Article, Product, BreadcrumbList, Organization, Recipe, Book, and MediaObject a template-level task, so it directly informs which page types to make eligible next. Recommended order: Product/Offer and Article first (broadest coverage and clearest CTR upside), BreadcrumbList second (cheap, site-wide, low risk), vertical types (Recipe/Book/MediaObject) last and only where the catalog genuinely holds every required field.json_encode() arrays inside a Blade partial. This package wins on consistency and reviewability (a fluent, typed builder rather than nested arrays reinvented per view), not on capability. Justify it as a two-to-three-day convenience decision, not a strategic platform dependency.Recipe or Product markup immediately exposes missing data — no author, no image, no price/availability, no rating. That's a useful forcing function for deciding whether those become first-class fields on the content model, and it's a decision worth making deliberately rather than by accident.Book/MediaObject/Recipe additions in 0.0.19 are schema.org output types, not application entities — resist letting this be scoped as a content-modeling initiative.Consider it when:
SchemaPresenter per type, plus a feature test that decodes the rendered JSON-LD and asserts required keys and no empty values.Look elsewhere or defer when:
0.0.19), ~148 stars, zero dependents, weak opportunity signal. Before committing, Engineering must confirm composer.json constraints resolve against your current Laravel/PHP versions — a 2020 package may declare Laravel 6–7-era illuminate/support bounds. If it doesn't install cleanly, the honest answer is "vendor a ~200-line presenter"; BSD-2-Clause makes that legally trivial and the codebase is small enough to fork in an afternoon.spatie/schema-org is actively maintained, more widely adopted, and covers more types. The comparison costs roughly a day — worth spending once, before anyone commits to a plan.@graph composition, multi-language handling, or built-in validation.Guardrails to set as PM: pin the exact version (0.0.19), wrap the package behind an internal interface so replacement is a one-file change per type, keep it out of controllers and Eloquent models, and treat it as an implementation detail that never appears in architecture or vendor-risk discussions. Add a validation gate (Rich Results Test + Schema Markup Validator per template, recurring Search Console check) and a written convention doc — one presenter per type, one @graph per page, stable @ids, no empty values, never fabricated ratings or availability — so the next developer doesn't re-derive it. Budget a few hours per Laravel major upgrade to confirm it still installs.
For executives: This is a small, low-cost SEO experiment, not a platform investment. Structured data is what makes our pages eligible for rich results — review stars, product prices, breadcrumb trails in search. We can pilot it on two templates (product detail and article) for roughly half a day of engineering each, measure the impression and CTR impact in Search Console, and expand or stop based on that data. Total exposure is a few days of work and no vendor contract. The main caveat is that the underlying library hasn't been updated since 2020, so we treat it as internal code: version-pinned, wrapped behind our own interface, with a documented fallback to either a maintained alternative or a small in-house replacement. Neither path changes the plan or the timeline.
For engineering: The package gives fluent, schema.org-friendly builders and Laravel facade/helper integration; it does not give model-to-schema mapping, @graph composition, escaping, or validation — those are ours, and they're the parts that actually determine whether rich results appear. Confine usage to a presenter layer (ArticlePresenter, ProductPresenter, …) that returns a plain array; Blade renders that array once in the layout head with JSON_HEX_TAG (never {{ }}, which entity-encodes and invalidates the block). Compose multiple entities into a single @graph with stable @ids rather than stacking script tags — duplicated markup is the most common cause of silent non-appearance. Strip null/''/[] recursively before encoding, use ISO 8601 dates and absolute canonical URLs, and test the rendered HTML rather than the presenter. First milestone: one template, one type, validated, merged. Not breadcrumbs on twelve templates.
The one thing to say out loud: the highest-severity failure mode here isn't technical, it's governance. Never emit placeholder values — no invented aggregateRating, no InStock for out-of-stock items, no stale prices. Fabricated structured data is a manual-action risk against the domain. If a template lacks the fields a type requires, that's a content-modelling decision to escalate, not a default to fill in.
How can I help you explore Laravel packages today?