Schema markup is what turns a plain blue link into a rich result with star ratings, prices, FAQs, or breadcrumbs. Most guides tell you to install an SEO plugin for it — but a plugin is just generating a block of JSON-LD text. You can write or generate that text yourself and paste it into your site in minutes, with zero plugin bloat and full control over the output.
Why skip the plugin?
SEO plugins are generalists: they output one-size-fits-all markup, occasionally emit duplicate blocks, and can't cover niche schema types like Course, JobPosting, or HowTo on custom post types. Adding schema manually gives you precision — one clean JSON-LD block per page, exactly the properties you need. It also sidesteps a classic failure: two plugins (or a plugin plus a theme) both emitting Article schema, which leaves Google unsure which block to trust.
Google recommends JSON-LD as the format for structured data. It lives inside a <script type="application/ld+json"> tag, separate from your visible HTML, so it works on WordPress, static sites, Shopify, Wix, Next.js — anything that lets you touch the page's <head>.
Step 0: Check what schema you already have
Before adding anything, check whether your site already emits structured data. Open any page in your browser's dev tools console and run:
document.querySelectorAll('script[type="application/ld+json"]').lengthIf the answer is 1 or more, read the blocks and note their types — you don't want to duplicate what your theme or an existing plugin already outputs. Google Search Console's Enhancements tab also lists the schema types Google has detected on your property, along with any error status.
Method 1: Custom HTML block in the WordPress editor (per-page)
The simplest plugin-free route for individual posts and pages. In the block editor, add a Custom HTML block anywhere in the post and paste your JSON-LD:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Your Article Title",
"author": { "@type": "Person", "name": "Your Name" },
"datePublished": "2026-09-23",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://yoursite.com/your-article-url"
}
}
</script>Replace the values with your real content, update, and you're done. The block is invisible to visitors but fully readable by crawlers. This is the best option when only a handful of pages need special markup — a LocalBusiness block on the contact page, FAQPage markup on a support article, and so on.
Method 2: header.php injection (site-wide or per-template)
For markup that belongs on every page — typically Organization or WebSite schema — edit your theme's header.php (Appearance > Theme File Editor) and paste the script just before the closing </head> tag. Edit a child theme, not the parent, or your edits vanish on the next theme update.
Better yet, don't edit theme files by hand at all: the Code Snippets plugin (a code-injection utility, not an SEO plugin) lets you add PHP snippets from the dashboard without touching files. This is also the cleanest way to do conditional logic — for example, outputting WebPage schema only on pages, and BlogPosting only on posts.
Method 3: functions.php hook with custom fields (dynamic, per-post)
If you want per-post schema without pasting code into the editor each time, pair a WordPress custom field with a wp_head hook. Add a custom field named schema_json to the post, paste your JSON-LD as its value, then add this to your child theme's functions.php:
function output_custom_schema() {
$schema = get_post_meta( get_the_ID(), 'schema_json', true );
if ( $schema ) {
echo '<script type="application/ld+json">' . $schema . '</script>';
}
}
add_action( 'wp_head', 'output_custom_schema' );Now any post with that field populated gets its own tailored schema, and posts without it get nothing. This pattern scales beautifully for sites with varied content types — events, products, recipes — where each post needs a different schema shape.
Method 4: Google Tag Manager (any platform, no code edits)
If you can't or won't edit templates — Shopify, Wix, Squarespace, or a locked-down WordPress install — use Google Tag Manager. Create a Custom HTML tag, paste your JSON-LD inside a <script type="application/ld+json"> tag, and set the trigger to fire on the pages that need it (All Pages for Organization schema, specific page paths for everything else). Preview, publish, and the container injects the markup at runtime.
One caveat: because GTM injects the script via JavaScript, verify with the Rich Results Test (which renders JavaScript) rather than a raw view-source check.
Method 5: Static HTML and modern frameworks
On a static site, paste the script tag directly into your HTML before </head>. In Next.js, use the framework's head management with dangerouslySetInnerHTML:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaObject) }}
/>In Nuxt, use useHead() or render the script tag in the component template. The principle is identical everywhere: JSON-LD is just a text blob inside a script tag.
Copy-ready JSON-LD templates
Always generate your markup with a real tool rather than hand-writing from memory: Google's Structured Data Markup Helper (tag elements on your live page, then click Create HTML) or Merkle's schema markup generator (fill a form, copy the JSON-LD). These cover required properties you'd otherwise miss. Three templates worth knowing:
- Article / BlogPosting — headline, author (Person), datePublished, dateModified, publisher (Organization), and image. A blog post is a
BlogPosting, not aNewsArticle— pick the most specific type available. - FAQPage — one caveat: Google retired FAQ rich results in May 2026, so FAQPage no longer buys you SERP real estate on Google (it can still feed AI citation and Bing). Don't build new FAQ sections solely for rich results.
- LocalBusiness — name, address, phone, opening hours, and geo coordinates. It's the highest-value markup for service-area businesses, and generators handle its many required fields better than memory does.
Validate before you trust it
Two free tools, and they test different things. Google's Rich Results Test (search.google.com/test/rich-results) checks whether your markup qualifies for specific rich result features — errors mean the markup won't qualify, while warnings just flag missing optional properties, so don't panic over warnings. The Schema.org Validator (validator.schema.org) checks structural correctness against the full schema.org vocabulary, catching malformed JSON and properties used on the wrong type. Use both: the validator confirms your code is correct, the Rich Results Test confirms Google can actually use it.
After deployment, watch Search Console's Enhancements section for 2–4 weeks while Google re-crawls and processes the new markup.
Mistakes that silently break schema
- Duplicate schema blocks. Two Article blocks on one page (theme + your manual addition) confuse Google. Audit first, add second.
- Marking up invisible content. If the page doesn't show star ratings, don't add AggregateRating. Fabricated data can trigger a manual action.
- Stale dates. When you update an article, update
dateModifiedtoo. - Invalid JSON. One trailing comma kills the whole block. Paste every block into a JSON validator before publishing — or better, use the Schema.org Validator.
- Wrong type specificity. BlogPosting for blog posts, Product with offers for store items, Event with location and startDate for events. Check each type's required properties before you ship.

