EthicsComputerCourses
Join training +91 90532 10052

Structured Data with JSON-LD

Digital Marketing & SEO · Lesson 10 of 30 Beginner 6 min read Updated

A person reading your page knows that "Ananya's Bakes, 14 MI Road, Jaipur, open 10 am to 9 pm" is a bakery with an address and opening hours. A program has to guess. Structured data removes the guessing: it is a standard, machine-readable description of the things on your page, such as a business, a product, an event or an article.

In this lesson you will write structured data in the JSON-LD format using the schema.org vocabulary, check it with a script in the browser, generate it with Python, and learn Google's rules for using it honestly.

What structured data does

Structured data uses a shared vocabulary from schema.org, a project supported by the major search engines. Each kind of thing has a type (LocalBusiness, Product, Event, Article) with properties (name, address, openingHoursSpecification).

Google uses structured data to understand a page and, for some types, to make the page eligible for rich results: search results with extra details such as product prices, event dates or recipe ratings. Two honest warnings:

  • Eligibility is not a guarantee. Google decides whether to show a rich result, and correct markup does not raise your ranking by itself.
  • Google supports only some types for rich results, and the list changes. For example, FAQ rich results stopped appearing in Google Search in May 2026, and HowTo rich results were removed in 2023. Always check Google's current Search Gallery before promising a client a rich result.

The JSON-LD format

Google recommends JSON-LD: JSON inside a <script type="application/ld+json"> element, usually in the <head>. It sits apart from your visible HTML, so it is easy to add and maintain. Here is a bakery described with the Bakery type, a more specific kind of LocalBusiness:

JSON
{
  "@context": "https://schema.org",
  "@type": "Bakery",
  "name": "Ananya's Bakes",
  "url": "https://www.example.com/",
  "telephone": "+91-XXXXXXXXXX",
  "priceRange": "₹₹",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "14 MI Road",
    "addressLocality": "Jaipur",
    "addressRegion": "Rajasthan",
    "postalCode": "302001",
    "addressCountry": "IN"
  },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
    "opens": "10:00",
    "closes": "21:00"
  }]
}

For local businesses, Google's documentation lists name and address as required, and recommends properties such as telephone, url, geo, openingHoursSpecification and priceRange. Use the most specific type that fits (Bakery, Dentist, AutoRepair) rather than the general LocalBusiness.

Check JSON-LD in the browser

This page contains JSON-LD and a small script that reads it, lists the properties and checks two things: the required properties exist, and the business name also appears in the visible heading. Run it, then delete the address block or change the <h1> and run again.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Ananya's Bakes, Eggless Cakes on MI Road, Jaipur</title>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Bakery",
    "name": "Ananya's Bakes",
    "url": "https://www.example.com/",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "14 MI Road",
      "addressLocality": "Jaipur",
      "postalCode": "302001",
      "addressCountry": "IN"
    },
    "openingHoursSpecification": [{
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
      "opens": "10:00",
      "closes": "21:00"
    }]
  }
  </script>
  <style>
    body { font-family: system-ui, sans-serif; margin: 1.5rem; }
    pre { background: #f5f5f5; padding: .8rem; }
  </style>
</head>
<body>
  <h1>Ananya's Bakes</h1>
  <p>Eggless cakes and cookies, 14 MI Road, Jaipur. Open 10 am to 9 pm, Monday to Saturday.</p>
  <pre id="report"></pre>
  <script>
    const lines = [];
    try {
      const data = JSON.parse(document.querySelector('script[type="application/ld+json"]').textContent);
      lines.push("Type: " + data["@type"]);
      for (const [key, value] of Object.entries(data)) {
        if (!key.startsWith("@")) lines.push(" - " + key + ": " + (typeof value === "object" ? "{...}" : value));
      }
      for (const required of ["name", "address"]) {
        if (!(required in data)) lines.push("ERROR: missing required property " + required);
      }
      const visible = document.querySelector("h1").textContent;
      lines.push(visible.includes(data.name) ? "OK: name matches the visible heading" : "WARNING: name not visible on the page");
    } catch (e) {
      lines.push("JSON error: " + e.message);
    }
    document.getElementById("report").textContent = lines.join("\n");
  </script>
</body>
</html>

This check is only a teaching aid. For real pages, use Google's Rich Results Test (which shows the rich results a page may be eligible for) and the Schema Markup Validator at validator.schema.org (which checks any schema.org markup). After launch, Search Console shows structured data reports for supported types.

The JSON trap: one comma breaks everything

JSON is strict. A trailing comma, a missing quote or a curly "smart quote" pasted from a word processor makes the whole block invalid, and search engines ignore it. Try this broken example and read the error:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Broken JSON-LD</title>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Bakery",
    "name": "Ananya's Bakes",
  }
  </script>
</head>
<body>
  <p id="out"></p>
  <script>
    try {
      JSON.parse(document.querySelector('script[type="application/ld+json"]').textContent);
      document.getElementById("out").textContent = "Valid JSON";
    } catch (e) {
      document.getElementById("out").textContent = "Invalid JSON: " + e.message;
    }
  </script>
</body>
</html>

Remove the comma after "Ananya's Bakes" and it becomes valid. This is why generating JSON-LD with code is safer than typing it by hand.

Generate JSON-LD with Python

A business with several branches needs the same markup with different details on each branch page. Python's json module always produces valid JSON:

PythonRun it yourself
import json

branches = [
    {"name": "Ananya's Bakes, MI Road", "street": "14 MI Road", "pin": "302001"},
    {"name": "Ananya's Bakes, Malviya Nagar", "street": "B-22 Malviya Nagar", "pin": "302017"},
]

for b in branches:
    data = {
        "@context": "https://schema.org",
        "@type": "Bakery",
        "name": b["name"],
        "address": {
            "@type": "PostalAddress",
            "streetAddress": b["street"],
            "addressLocality": "Jaipur",
            "postalCode": b["pin"],
            "addressCountry": "IN",
        },
    }
    print('<script type="application/ld+json">')
    print(json.dumps(data, ensure_ascii=False))
    print("</script>")
Output
<script type="application/ld+json">
{"@context": "https://schema.org", "@type": "Bakery", "name": "Ananya's Bakes, MI Road", "address": {"@type": "PostalAddress", "streetAddress": "14 MI Road", "addressLocality": "Jaipur", "postalCode": "302001", "addressCountry": "IN"}}
</script>
<script type="application/ld+json">
{"@context": "https://schema.org", "@type": "Bakery", "name": "Ananya's Bakes, Malviya Nagar", "address": {"@type": "PostalAddress", "streetAddress": "B-22 Malviya Nagar", "addressLocality": "Jaipur", "postalCode": "302017", "addressCountry": "IN"}}
</script>

ensure_ascii=False keeps characters such as ₹ readable instead of turning them into escape codes; both forms are valid JSON.

Rules for honest markup

Google's structured data guidelines come down to one idea: markup must describe what is really on the page.

  • Mark up only content that visitors can see. Do not add hidden prices, fake ratings or events that are not on the page.
  • Do not mark up your own reviews of yourself for star ratings. Google does not show review stars for LocalBusiness or Organization pages where the business controls the reviews about itself, including reviews pulled in through a widget.
  • Never include fake reviews or reviews given in exchange for discounts or free products without clear disclosure.

Misleading markup can lead to a manual action in Search Console, which removes your eligibility for rich results.

Common mistakes

  • Copying markup from another site and forgetting to change the name, address or URL.
  • Invalid JSON from trailing commas or smart quotes.
  • Markup that does not match the page, such as opening hours that differ from the visible hours.
  • Expecting stars for your own business. Self-serving review markup is not eligible for review snippets.
  • Using retired features. FAQ and HowTo markup no longer produce rich results in Google Search.

Key points

  • Structured data describes page content in a standard vocabulary (schema.org); Google recommends JSON-LD.
  • It helps Google understand pages and may make them eligible for rich results, with no guarantee.
  • For local businesses, name and address are required; use the most specific type such as Bakery.
  • Test with the Rich Results Test and the Schema Markup Validator; generate JSON with code to avoid syntax errors.
  • Mark up only visible, truthful content; self-serving and fake review markup is not allowed.

Exercise

Write JSON-LD for a dental clinic in Kochi using the Dentist type. Include name, a full address with addressCountry set to IN, telephone with a placeholder number, and opening hours from 9:00 to 18:00, Monday to Saturday. Paste it into the first Try-it example (replacing the bakery data and heading) and make sure the check shows no errors.

Show solution
JSON
{
  "@context": "https://schema.org",
  "@type": "Dentist",
  "name": "Smile Care Dental Clinic",
  "telephone": "+91-XXXXXXXXXX",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "2nd Floor, MG Road",
    "addressLocality": "Kochi",
    "addressRegion": "Kerala",
    "postalCode": "682016",
    "addressCountry": "IN"
  },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    "opens": "09:00",
    "closes": "18:00"
  }]
}

Change the <h1> to "Smile Care Dental Clinic" so the name check passes. Dentist is a specific type of LocalBusiness, which describes the clinic more precisely.

Quick Quiz

1. Which format does Google recommend for structured data?

2. You add correct Product markup. What happens?

3. Can a bakery mark up reviews about itself on its own site to get star ratings?

4. What is wrong with {"name": "Ananya's Bakes",}?

Learn Digital Marketing & SEO with a mentor

Free lessons get you started. Our Digital Marketing & SEO training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call