Loomaly

JSON-LD viewer and structured data checker

Paste a URL to see the page's JSON-LD drawn as a graph: what the page says about itself, how the pieces connect through @id references, and which references point nowhere. It also checks required properties and value formats.

What JSON-LD is, and why it's a graph

JSON-LD is structured data written as JSON inside a <script type="application/ld+json"> tag. It describes the page in the schema.org vocabulary (this is a Product called Blue Mug, it costs 24 USD, it is sold by this Organization) so that search engines and AI assistants don't have to guess from the layout. Google reads JSON-LD, Microdata and RDFa, and recommends JSON-LD.

Every object has an @type. It can also have an @id, an identifier that is usually the page URL plus a fragment, such as https://example.com/#organization. Any other object can then point at it by that @id instead of repeating it. That is how one page says: this WebPage belongs to this WebSite, the WebSite is published by this Organization, and the Product on the page is made by the same Organization.

When the references line up, the page describes one connected set of facts. When one doesn't, because of a typo in an @id or a pointer to an Organization that only the homepage prints, every block still parses and the connection is silently lost. A list of properties won't show you that. A graph does: the entity that should be connected sits on its own, or an arrow ends at a dashed card.

How to read the graph

  • Each card is one entity. The coloured labels are its @type. Under them is its name (or headline, title or URL) and up to four of its plain-text properties.
  • An arrow runs from the entity that has the property to the entity it points at, labelled with the property name: offers, brand, publisher, author, isPartOf.
  • Entities that nothing points at sit in the left column. Each column to the right is one step further down.
  • A dashed card marked “Referenced, never defined” is an @id that something points at but nothing on the page describes.
  • Objects that share an @id are merged into one card, even when they sit in separate script blocks. If you expected two cards and see one, they share an @id.
  • Only JSON-LD is read, not Microdata or RDFa. Very large graphs show their first 150 entities. Download PNG saves the graph on a white background, ready for a ticket or a client report.

What the score and the problems mean

The score runs from 0 to 100 and weighs six things: whether the JSON-LD is well-formed (20%), whether the properties Google requires for a rich result are there (30%), whether values have the right format (20%), recommended properties (15%), whether @id references resolve (10%), and small best practices (5%). A page with no JSON-LD scores 0.

Problems are listed errors first. Each one starts with a path such as Product.offers.price that you can search your template for. These are the messages you'll see, and what to do about each:

block is not valid JSON
A syntax error, so the whole block is ignored. The usual causes are a trailing comma, an unescaped double quote inside a string, or a template variable that printed nothing. Run the block through a JSON parser and fix the line it points to.
@context is missing
Without @context the property names mean nothing. Add "@context": "https://schema.org" to each top-level object. If yours says http://schema.org, that still works; https is the preferred form, so it only shows as a notice.
node has no @type
An object with properties but no type can't be interpreted. Add the @type that says what it is, such as Offer, Person or PostalAddress.
… requires …; without it the rich result is not eligible
Google lists that property as required for the type's rich result. An Event needs name, startDate and location; a BreadcrumbList needs itemListElement. Add it, using the real value shown on the page.
… should include …
A recommended property is missing. The markup is still eligible without it, but the result can show less. Add it only if it's true: don't invent a rating or a review count to clear a notice.
expected a number / an absolute http(s) URL / an ISO 8601 date / a nested object with an @type
The value has the wrong shape. A price of "$24.00" should be 24.00, with the currency in priceCurrency. A date like "Sept 3, 2026" should be "2026-09-03". An image path like /img/mug.jpg should be the full URL. An author given as plain text should be a Person or Organization object with a name. Values from a fixed list, such as availability, must be one of the listed ones, like https://schema.org/InStock.
… holds …, expected …
The nested object has the wrong type, such as an address that holds a Person. Change its @type to the one the message names.
positions … must run 1..n with no gaps / every crumb except the last needs an item
In a BreadcrumbList, position must count 1, 2, 3 with no gaps, and every crumb except the last one (the current page) needs an item holding that crumb's URL.
@id "…" is referenced but never defined on this page
This is the dashed card in the graph: something points at an @id that nothing on this page describes. Usually it's a typo (#org instead of #organization, http instead of https, a missing trailing slash) or the entity is only printed on another page, often the Organization on the homepage. A reference is not a link, and nothing goes to fetch it, so fix the spelling or output the entity on this page too.
… separate entities with no @id cross-references between them
Several top-level entities that never point at each other. That's valid, but they read as unrelated facts. Give each one an @id and connect them: the WebPage's isPartOf to the WebSite, an Article's publisher to the Organization.
… has no rich result we can check for
We don't have rules for that type. It isn't an error: the entity still appears in the graph, and anything nested inside it is still checked.

A worked example

A product page prints an Organization, a WebPage and a Product in one @graph. Every part parses, and at a glance it looks fine:

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://example.com/#organization",
      "name": "Example Coffee Co.",
      "url": "https://example.com/",
      "logo": "https://example.com/logo.png"
    },
    {
      "@type": "WebPage",
      "@id": "https://example.com/products/blue-mug#webpage",
      "name": "Blue Mug",
      "url": "https://example.com/products/blue-mug"
    },
    {
      "@type": "Product",
      "@id": "https://example.com/products/blue-mug#product",
      "name": "Blue Mug",
      "image": "https://example.com/img/blue-mug.jpg",
      "mainEntityOfPage": { "@id": "https://example.com/products/blue-mug#webpage" },
      "brand": { "@id": "https://example.com/#org" },
      "offers": {
        "@type": "Offer",
        "price": "$24.00",
        "priceCurrency": "USD",
        "availability": "https://schema.org/InStock"
      }
    }
  ]
}

The graph shows what the code hides. The Organization sits alone in the left column with nothing pointing at it, and the Product's brand arrow ends at a dashed card for https://example.com/#org. The problem list says the same, with paths:

error  Product.offers.price
       price on Offer: expected a number, got the bare text "$24.00"
error  Product.brand
       @id "https://example.com/#org" is referenced but never defined on this page

The fix is two lines: point brand at the @id the Organization actually uses, and take the currency symbol out of the price.

"brand": { "@id": "https://example.com/#organization" },
…
  "price": 24.00,

The score goes from 79 to 94 and the Organization is now connected to the Product. The four notices left ask for sameAs, sku, aggregateRating and the offer's url. Add the ones that are true for the shop; a product without reviews shouldn't carry a rating.

Questions

Why does the tool find no JSON-LD on a page that has some?

It reads the HTML your server sends and doesn't run JavaScript, so JSON-LD added by a script, such as a tag manager or a client-side app, won't appear. Google can read JSON-LD that JavaScript injects, but many other crawlers and AI tools don't run scripts, so markup in the server HTML is the safer choice. Also check that the markup is JSON-LD rather than Microdata or RDFa, and that the site isn't blocking automated requests.

How is this different from Google's Rich Results Test and the Schema Markup Validator?

The Rich Results Test loads the page the way Google does, JavaScript included, and tells you which Google rich results it is eligible for. The Schema Markup Validator at validator.schema.org checks markup against the whole schema.org vocabulary. This tool draws how the entities connect, which neither of them shows, and its checks cover the common rich-result types: Article, Product, Offer, BreadcrumbList, Event, Recipe, VideoObject, LocalBusiness, Organization, WebSite and a few more. Before you count on a specific Google feature, confirm it in the Rich Results Test.

Does an @id have to be a real URL?

No. It's an identifier; nobody needs to be able to open it. The common convention is the page URL plus a fragment: https://example.com/#organization for things that are the same on every page, https://example.com/products/blue-mug#product for things that belong to one page. What matters is that the string is identical everywhere it's used. http and https, www and no www, and a trailing slash each make a different @id.

If no problems are found, will I get rich results?

Not necessarily. Correct markup makes a page eligible; Google decides page by page and search by search whether to show a rich result, and the markup has to describe content that visitors can see on the page. Some features have also been retired: Google stopped showing FAQ rich results in May 2026, and since January 2025 breadcrumb trails appear only in desktop results.

Can I check a page that isn't public, like a staging site?

No. The tool only fetches public http(s) addresses on the standard ports. IP addresses, localhost and internal hostnames are refused, and every redirect is checked the same way. For a page behind a login or on your own machine, paste the code into Google's Rich Results Test, which accepts code snippets.