Structured Data and CMS: A Practical Guide

Structured Data and CMS: A Practical Guide

Structured data has a reputation for being complicated, mostly because plugin-driven implementations bury it behind settings screens. The underlying format is genuinely simple, and most CMS platforms — headless or otherwise — can generate it directly from content that already exists. This is the practical version: what to implement, and how.

Table of Contents
  1. JSON-LD Is the Format to Use
  2. The High-ROI Schema Types Worth Prioritizing
  3. Two Implementation Approaches
  4. Plugin-Driven (Traditional CMS)
  5. Template-Driven (Headless or Structured-Content CMS)
  6. A Real Example: EmDash's page:metadata Hook
  7. Practical Structured Data Checklist
  8. Validating What You've Built
  9. Frequently Asked Questions
  10. Does the position of the JSON-LD script tag in the page matter?
  11. Can I have multiple JSON-LD blocks on one page?
  12. Is structured data itself a direct ranking factor?
  13. Do I need structured data if my CMS doesn't have a dedicated SEO plugin?
  14. The Bottom Line
  15. Sources

JSON-LD Is the Format to Use

JSON-LD is trivial to generate programmatically from a CMS, API, or database, making it the preferred format. Google recommends using JSON-LD for structured data whenever possible.

JSON-LD is a script tag containing a JSON object describing your page's content in a standardized vocabulary (schema.org). Unlike older microdata or RDFa formats, it doesn't need to be woven into your visible HTML — it's a separate block, easy to generate from a template without touching your page's actual markup.

The High-ROI Schema Types Worth Prioritizing

Common high-ROI candidates are Product (e-commerce), LocalBusiness (local), BreadcrumbList (navigation), Article (editorial), and FAQPage or HowTo when the content genuinely fits.

That "genuinely fits" qualifier matters — schema markup that doesn't accurately describe your actual content is a real risk, not just wasted effort. Worth flagging a specific 2026 development here too: Google stopped showing FAQ rich results in search for all sites as of May 2026. The FAQPage schema type itself remains valid and won't cause any issues if it stays on your pages, but don't expect the rich-result visibility boost that used to come with it.

Read also:

Two Implementation Approaches

Plugin-Driven (Traditional CMS)

On a platform like WordPress, dedicated SEO plugins (Yoast, Rank Math, Schema Pro) automate JSON-LD generation for common types through their own settings UI. This is genuinely convenient for a non-technical team, at the cost of being locked into whatever schema types and customization options that specific plugin supports.

Template-Driven (Headless or Structured-Content CMS)

When your CMS exposes a hook or template variable system that lets you emit structured data directly from content fields, you write the JSON-LD generation once per content type, pulling from fields that already exist — no separate plugin, no settings screen to keep in sync as your content model changes.

A Real Example: EmDash's page:metadata Hook

EmDash is a concrete example of the template-driven approach — its `page:metadata` hook lets a plugin (or, in a simpler setup, logic in your Astro templates) contribute JSON-LD directly from the page's actual content, validated and deduplicated by the platform automatically:

"page:metadata": async (event, ctx) => {
  if (event.page.kind !== "content") return null;

  return {
    kind: "jsonld",
    id: `schema:${event.page.content?.collection}:${event.page.content?.id}`,
    graph: {
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      headline: event.page.pageTitle ?? event.page.title,
      description: event.page.description,
      datePublished: event.page.content?.publishedAt,
    },
  };
},
emdashkits.com

That's the entire implementation for Article/BlogPosting schema across every post — no plugin settings screen, and it stays in sync automatically since it reads the same fields your page already renders.

Practical Structured Data Checklist

  • Article/BlogPosting schema on every blog post, pulling headline, description, and publish date from existing fields.
  • BreadcrumbList schema on any page with a real navigational hierarchy (category → subcategory → post).
  • Organization or LocalBusiness schema site-wide, for brand/entity recognition.
  • Product schema on every commerce page, if applicable — one of the highest-ROI types where it fits.
  • Person schema on author/byline pages, linking back to their authored content.
  • Skip FAQPage purely for the rich-result visibility as of 2026 — implement it only if the content is genuinely FAQ-structured for its own sake.

Validating What You've Built

Google's Rich Results Test checks whether your JSON-LD is correct and what rich results it can actually generate; the independent Schema Markup Validator checks conformance against the schema.org vocabulary itself, regardless of whether Google currently does anything visible with that specific type. Run both — a markup error that Google silently ignores today can still matter for other consumers of structured data (AI systems, other search engines) even without an immediate visible SEO effect.

Frequently Asked Questions

Does the position of the JSON-LD script tag in the page matter?

No — it can go in the head or body, and position doesn't affect how search engines parse it. Most implementations place it in the head purely for code organization, not because it's required there.

Can I have multiple JSON-LD blocks on one page?

Yes — a single page commonly carries several schema types simultaneously (Article plus BreadcrumbList plus Organization, for instance), either as separate script tags or an array within one.

Is structured data itself a direct ranking factor?

Not directly — it doesn't boost rankings on its own. It enables rich results (star ratings, breadcrumbs in search listings, FAQ snippets where still shown) that improve click-through rate, and it helps search engines and AI systems understand your content's meaning more precisely, which is an indirect but real benefit.

Do I need structured data if my CMS doesn't have a dedicated SEO plugin?

You need a way to generate it, whether that's a plugin, a template-level hook like EmDash's `page:metadata`, or hand-written script tags in your page templates — the mechanism matters less than actually having valid, accurate structured data on your key content types.

The Bottom Line

Structured data implementation comes down to picking a mechanism (plugin or template-driven) and prioritizing the schema types that actually match your content — Article, BreadcrumbList, Organization, and Product cover most of the real value. See our guide to optimizing an EmDash site for SEO for the full metadata picture beyond structured data alone.

Sources

Share

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Fix
  4. Lessons learned

Diagnostic

Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.

Root cause

// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com

A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.

Read also:

Fix

// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com

Lessons learned

  • Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
  • "Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
  • Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
Share
Previous Article

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Comments

Write a comment

Related Articles

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Lessons learned

Diagnostic

Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.

Root cause

The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.

// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
emdashkits.com
Read also:

Lessons learned

  • "No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
  • Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Share
Previous Article

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Next Article

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)