JSON-LD (JavaScript Object Notation for Linked Data) is the recommended format for adding schema.org structured data to blog posts. It lives in a single <script> tag, does not touch your HTML, and — critically — must be hard-coded in static HTML so AI crawlers that skip JavaScript execution can still read it. This guide walks through a production-ready BlogPosting implementation.

Why JSON-LD over Microdata and RDFa

There are three ways to add schema.org markup: JSON-LD, Microdata, and RDFa. Google recommends JSON-LD. AI engines prefer it too. If you are new to the vocabulary itself, read Schema.org Structured Data: A Complete Step-by-Step Guide first.

The key reasons:

  • Separation of concerns — JSON-LD lives in a <script> tag, entirely separate from your HTML structure. You can add, update, or remove it without touching your content markup.
  • Readability — it is plain JSON. Anyone can read, write, and debug it without learning a new attribute syntax.
  • Machine processing — parsers process it in one pass. Microdata requires traversing the DOM; JSON-LD is a self-contained data file.

The static HTML rule: why it matters for AI

This is the most common implementation mistake, and it silently voids all your work.

AI crawlers — GPTBot (ChatGPT), PerplexityBot (Perplexity), ClaudeBot (Anthropic) — are built for speed and efficiency. Many of them do not execute JavaScript. They parse the raw HTML your server delivers and move on.

If your JSON-LD is injected by a tag manager, a React useEffect, or any JavaScript that runs after initial page load, these bots receive a page with no structured data. Your schema does not exist from their perspective.

Rule: JSON-LD must be present in the server-delivered HTML, inside <head>, before any JavaScript executes. If you want to go further with Q&A content, see FAQPage Schema: How to Get Your FAQs Cited by AI Answer Engines.

Every major CMS supports this:

  • WordPress (Rank Math / Yoast) — schema is injected server-side automatically
  • Webflow — Head code field under Page Settings
  • Ghost — Code injection → Site header
  • Framer — Custom Code → <head> section
  • Next.jsdangerouslySetInnerHTML in a Server Component (renders at build time or request time, never client-side)

A production-ready BlogPosting schema

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "BlogPosting",
      "@id": "https://yoursite.com/articles/your-article-slug#article",
      "headline": "Your Article Title",
      "description": "A one-sentence summary of the article for AI extraction.",
      "datePublished": "2026-05-01",
      "dateModified": "2026-05-01",
      "isAccessibleForFree": true,
      "inLanguage": "en",
      "url": "https://yoursite.com/articles/your-article-slug",
      "author": {
        "@type": "Person",
        "name": "Author Full Name",
        "url": "https://yoursite.com/about"
      },
      "publisher": {
        "@type": "Organization",
        "name": "Your Brand Name",
        "logo": {
          "@type": "ImageObject",
          "url": "https://yoursite.com/logo.png"
        }
      },
      "image": {
        "@type": "ImageObject",
        "url": "https://yoursite.com/articles/your-article-og.png",
        "width": 1200,
        "height": 630
      },
      "speakable": {
        "@type": "SpeakableSpecification",
        "cssSelector": ["h1", ".article-summary"]
      },
      "keywords": "keyword one, keyword two, keyword three"
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://yoursite.com" },
        { "@type": "ListItem", "position": 2, "name": "Articles", "item": "https://yoursite.com/articles" },
        { "@type": "ListItem", "position": 3, "name": "Your Article Title", "item": "https://yoursite.com/articles/your-article-slug" }
      ]
    }
  ]
}

Field-by-field breakdown

headline — the article title exactly as it appears on the page. Maximum 110 characters for Google rich results.

description — a complete, standalone summary in 1–2 sentences. This is what AI engines use as the answer text when they cite you. Write it as if it were the only text an AI will see from your article.

datePublished / dateModified — ISO 8601 format (YYYY-MM-DD). Keep dateModified current. Stale modification dates reduce AI citation likelihood for time-sensitive topics.

isAccessibleForFree: true — mark this explicitly if your content is free to read. AI engines use it to confirm they can reproduce it without a paywall concern.

speakable — identifies the sentences a voice assistant should read aloud. Point the cssSelector at your article's first paragraph or a dedicated summary block. Keep the targeted text to 280–350 characters.

@id fragments — use #article, #breadcrumb, etc. as stable identifiers that let engines link entities across the graph without ambiguity.

How to place the script in <head>

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Your Article Title</title>
  
  <!-- Schema.org JSON-LD — must be here, not in <body> or loaded by JS -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    ...
  }
  </script>
</head>
<body>
  ...
</body>
</html>

The script tag's position in <head> means it is the "first thing to fire" when a bot arrives. Bots process the head before the body, so your schema is guaranteed to be read regardless of how far down the page the actual article content appears.

Validating your implementation

After adding the script, always validate before deploying:

  1. Schema.org Validatorvalidator.schema.org — checks structural correctness
  2. Google Rich Results Test — confirms BlogPosting is eligible for rich results
  3. View sourceCtrl+U or Cmd+U in your browser — confirm the JSON-LD appears in the raw HTML, not injected after load

If the schema only appears after a delay when using browser developer tools' "Elements" panel but is absent in "View Source", it is being injected by JavaScript. Fix this before indexing.

Common questions about JSON-LD for blogs

Does every blog post need its own JSON-LD?

Yes. Each post has a unique URL, title, description, and publication date. A single generic schema at the site level does not substitute for per-article structured data. Use a template in your CMS or static site generator to auto-generate the schema from post frontmatter.

What happens if my JSON-LD has an error?

A syntax error (missing comma, unclosed bracket) silently invalidates the entire block. The page still renders normally for users, but bots skip the schema. Always validate with the Schema.org Validator before and after any CMS update that touches the head section.

Should I use Article or BlogPosting?

BlogPosting is a subtype of Article — it is more specific and preferred for blog content. Use Article for editorial or news content, BlogPosting for blog posts, and NewsArticle for time-sensitive journalism. The difference is minor for AI citation purposes, but precision helps.