DEV Community

Cover image for Article Schema Markup: The Complete Guide to Structured Data for Blog Posts and Articles
Roman Popovych
Roman Popovych

Posted on

Article Schema Markup: The Complete Guide to Structured Data for Blog Posts and Articles

There's a pattern I keep seeing. A developer spends real time writing a useful technical post — not SEO fluff, actual useful content — it gets indexed, and then they search for the exact topic it covers. Their competitor's article shows author attribution, a publish date, maybe a thumbnail image. Theirs shows a plain blue link with a meta description.

Article Schema is usually part of that gap. Not all of it — domain authority matters too — but structured data is the piece you actually control.

What's in this guide: the difference between Article, NewsArticle, and BlogPosting (it's not obvious), image size requirements that most guides get wrong, how author markup connects to E-E-A-T scoring, and working implementations for Next.js, React, and WordPress.


What Is Article Schema Markup?

Article Schema is a structured data type from schema.org/Article that tells search engines — in machine-readable JSON-LD format — that a page contains editorial content: an article, blog post, news story, or tutorial.

Without it, Google has to infer this from your HTML structure, heading hierarchy, and content signals. With it, you're stating it explicitly. That unlocks specific rich result types that aren't available to unstructured pages.

The script block sits in your <head> or <body>:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Your Article Title Here"
}
</script>
Enter fullscreen mode Exit fullscreen mode

Article vs NewsArticle vs BlogPosting: Which Type to Use

Schema.org has a hierarchy for article content. Choosing the wrong type won't break anything, but choosing the right one signals your content category more accurately to Google.

Type Use when Rich result eligibility
Article General editorial content, catch-all Standard article results
NewsArticle Time-sensitive news from a recognized publisher Top Stories carousel, News tab
BlogPosting Personal or company blog posts Standard article results
TechArticle Technical documentation, tutorials, how-to guides Standard article results

Quick decision logic:

  • News publisher → NewsArticle
  • Dev blog, company blog → BlogPosting
  • Tutorials, API docs, technical how-tos → TechArticle
  • Everything else → Article

BlogPosting and TechArticle are both subtypes of Article, so they inherit all the same fields. NewsArticle has a few additional fields specific to journalism (like dateline and printEdition).

For most developers writing technical content on dev.to, personal blogs, or company engineering blogs: BlogPosting or TechArticle is the correct choice.


What Rich Results Article Schema Unlocks

Article structured data doesn't guarantee specific rich result features — Google decides what to display based on query context, site authority, and content freshness. But it makes you eligible for things that are otherwise impossible:

Feature What appears Requirements
Top Stories carousel Large image + headline + source in a horizontal carousel NewsArticle, AMP or fast page, news publisher
Article image in results Thumbnail next to the snippet image with correct dimensions
Author attribution "By [Author Name]" in some result layouts author with @type: Person
Breadcrumb in URL Site › Category › Article format BreadcrumbList schema (separate)
Sitelinks Sub-links below the main result WebSite schema + site authority
Paywalled content Paywall indicator and preview snippet isAccessibleForFree + hasPart

For an independent developer blog, the realistic wins are image display in search results and author E-E-A-T signals. Top Stories requires a news publisher relationship with Google. Sitelinks come from site authority, not schema. Know which goals are actually achievable before you start.


Google's Image Requirements for Article Rich Results

This is where most implementations fail silently. Google's Rich Results Test will show your schema as valid, but the image won't appear because it doesn't meet the dimension requirements.

The rules:

  • Minimum 50,000 pixels (width × height ≥ 50,000) — for example, 300×167px qualifies; anything smaller is ignored
  • Recommended aspect ratios: 16:9, 4:3, and 1:1 — provide all three for maximum placement flexibility across different result layouts
  • Format: JPG, PNG, WebP — avoid SVG or GIF
  • Must be crawlable — no auth walls, no X-Robots-Tag: noindex on the image URL
  • Must represent the article content — a logo, icon, or decorative image doesn't qualify

In practice, aim for much higher resolution than the technical minimum. A 1200×675px image (16:9, ~810K pixels) is the widely accepted practical target — it satisfies the pixel requirement with significant headroom and renders sharply across all display contexts.

The safest approach is to provide three image URLs covering all three aspect ratios:

"image": [
  "https://example.com/articles/js-tips-16x9.jpg",
  "https://example.com/articles/js-tips-4x3.jpg",
  "https://example.com/articles/js-tips-1x1.jpg"
]
Enter fullscreen mode Exit fullscreen mode

If you only have one image, 1200×675px (16:9) is the practical sweet spot — well above the 50K pixel floor and covers the most common display context.


The Minimum Valid Article Schema

Google states explicitly: Article schema has no required properties. Instead, it recommends including the fields that apply to your content. In practice, headline, image, datePublished, and author are the four highest-impact fields — omitting any of them significantly reduces the chance of rich result display. Here's the smallest useful block:

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How I Optimized My React App's Bundle Size by 60%",
  "image": "https://example.com/articles/bundle-optimization.jpg",
  "datePublished": "2026-05-08",
  "author": {
    "@type": "Person",
    "name": "Alex Johnson"
  }
}
Enter fullscreen mode Exit fullscreen mode

This passes validation. But it's still the floor. A production article deserves the full set.


The Complete Article Schema: All Recommended Fields

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How I Optimized My React App's Bundle Size by 60%",
  "description": "A practical breakdown of code splitting, tree shaking, and lazy loading techniques that reduced a production React app's initial bundle from 1.8MB to 720KB.",
  "image": [
    "https://example.com/articles/bundle-optimization-16x9.jpg",
    "https://example.com/articles/bundle-optimization-4x3.jpg",
    "https://example.com/articles/bundle-optimization-1x1.jpg"
  ],
  "datePublished": "2026-05-08T09:00:00+00:00",
  "dateModified": "2026-05-08T09:00:00+00:00",
  "author": {
    "@type": "Person",
    "name": "Alex Johnson",
    "url": "https://example.com/authors/alex-johnson",
    "sameAs": [
      "https://twitter.com/alexjohnson",
      "https://github.com/alexjohnson",
      "https://linkedin.com/in/alexjohnson"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "Example Dev Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/logo.png",
      "width": 600,
      "height": 60
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://example.com/articles/react-bundle-optimization"
  },
  "url": "https://example.com/articles/react-bundle-optimization",
  "wordCount": 2400,
  "articleSection": "Performance",
  "keywords": ["React", "bundle size", "webpack", "code splitting", "performance"],
  "inLanguage": "en-US"
}
Enter fullscreen mode Exit fullscreen mode

Field Reference: Required, Recommended, and Optional

Field Type Priority Notes
headline Text Strongly recommended Max 110 characters. Match your <h1>, not the <title>
image URL or array Strongly recommended Min 50K pixels (width × height); 1200×675px is the practical target
datePublished DateTime Strongly recommended ISO 8601: "2026-05-08" or "2026-05-08T09:00:00+00:00"
author Person or Organization Strongly recommended Must include name; url strongly recommended; no job titles in name
dateModified DateTime Strongly recommended Signals freshness to Google; update on significant edits
publisher Organization Strongly recommended Required for Top Stories; include logo
description Text Recommended Different from meta description — can be longer
mainEntityOfPage WebPage Recommended Links the article to its canonical URL
url URL Recommended Canonical URL of the article
wordCount Integer Optional Helps Google understand content depth
articleSection Text Optional Category name ("Technology", "Performance", "Security")
keywords Text or array Optional Topic tags — not a ranking signal, but improves understanding
inLanguage Text Optional BCP 47 language tag: "en-US", "uk", "de"
isAccessibleForFree Boolean Conditional Required if content is paywalled

Author Markup and E-E-A-T

Google's E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) guidelines place heavy weight on knowing who wrote a piece of content, especially for health, finance, legal, and technical topics.

Author markup in structured data is one of the signals Google uses to evaluate this. A bare "name": "Alex" is valid but weak. A full author object with a linked author page and verified social profiles is significantly stronger:

"author": {
  "@type": "Person",
  "name": "Roman Popovych",
  "url": "https://devtools.abect.com/about",
  "sameAs": [
    "https://linkedin.com/in/yourprofile",
    "https://github.com/yourhandle",
    "https://twitter.com/yourhandle"
  ],
  "jobTitle": "Full-Stack Software Engineer",
  "worksFor": {
    "@type": "Organization",
    "name": "Freelance"
  }
}
Enter fullscreen mode Exit fullscreen mode

Important: Google explicitly states that author.name should contain only the person's name — no job titles, honorifics (Dr., Prof.), or organizational affiliations. Put those in jobTitle and worksFor, not inside name. Violating this causes a validation warning.

The sameAs array is particularly valuable. It links your author entity to established profiles on platforms Google already trusts. This helps Google build a knowledge graph entity for you as an author — which strengthens the authority signal for everything you publish.

Your url should point to a dedicated author page (not your homepage) that includes your name, photo, bio, and links to your work. That page should also contain Person JSON-LD that mirrors this markup.


Multiple Authors

When an article has more than one author, author accepts an array:

"author": [
  {
    "@type": "Person",
    "name": "Alex Johnson",
    "url": "https://example.com/authors/alex"
  },
  {
    "@type": "Person",
    "name": "Maria Chen",
    "url": "https://example.com/authors/maria"
  }
]
Enter fullscreen mode Exit fullscreen mode

For organizational authorship (when no individual author is credited), use Organization instead of Person:

"author": {
  "@type": "Organization",
  "name": "Example Engineering Team",
  "url": "https://example.com/team"
}
Enter fullscreen mode Exit fullscreen mode

Implementation in Next.js (App Router)

In Next.js App Router, generate the JSON-LD object in the server component and inject it as a <script> tag. Don't generate it client-side — Google needs it in the initial HTML response.

// app/blog/[slug]/page.tsx

import { getPost } from '@/lib/posts'

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    description: post.excerpt,
    image: post.coverImage,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt ?? post.publishedAt,
    author: {
      '@type': 'Person',
      name: post.author.name,
      url: `https://yoursite.com/authors/${post.author.slug}`,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Your Blog Name',
      logo: {
        '@type': 'ImageObject',
        url: 'https://yoursite.com/logo.png',
        width: 600,
        height: 60,
      },
    },
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': `https://yoursite.com/blog/${params.slug}`,
    },
    url: `https://yoursite.com/blog/${params.slug}`,
    wordCount: post.wordCount,
    articleSection: post.category,
    keywords: post.tags,
    inLanguage: 'en-US',
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>
        <h1>{post.title}</h1>
        {/* article content */}
      </article>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

For generateMetadata, keep it separate — don't try to merge JSON-LD into the metadata object. They serve different purposes.


Implementation in React (with react-helmet-async)

import { Helmet } from 'react-helmet-async'

function BlogPost({ post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    description: post.excerpt,
    image: post.coverImage,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt || post.publishedAt,
    author: {
      '@type': 'Person',
      name: post.author.name,
      url: post.author.profileUrl,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Your Blog',
      logo: {
        '@type': 'ImageObject',
        url: 'https://yoursite.com/logo.png',
        width: 600,
        height: 60,
      },
    },
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': post.url,
    },
    url: post.url,
  }

  return (
    <>
      <Helmet>
        <script type="application/ld+json">
          {JSON.stringify(jsonLd)}
        </script>
      </Helmet>
      <article>
        <h1>{post.title}</h1>
        {/* content */}
      </article>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

For React SPAs: Google processes JavaScript in a second crawl wave, which can delay indexing. If SEO matters for your blog, invest in SSR or static generation.


Implementation in WordPress (without a plugin)

WordPress with a classic theme doesn't add Article schema by default. Add it to functions.php for single post pages:

function add_article_schema() {
  if ( ! is_single() ) return;

  $author_id = get_post_field( 'post_author', get_the_ID() );

  $schema = [
    '@context'        => 'https://schema.org',
    '@type'           => 'BlogPosting',
    'headline'        => get_the_title(),
    'description'     => get_the_excerpt(),
    'image'           => get_the_post_thumbnail_url( get_the_ID(), 'full' ),
    'datePublished'   => get_the_date( 'c' ),
    'dateModified'    => get_the_modified_date( 'c' ),
    'url'             => get_permalink(),
    'author'          => [
      '@type' => 'Person',
      'name'  => get_the_author_meta( 'display_name', $author_id ),
      'url'   => get_author_posts_url( $author_id ),
    ],
    'publisher'       => [
      '@type' => 'Organization',
      'name'  => get_bloginfo( 'name' ),
      'logo'  => [
        '@type' => 'ImageObject',
        'url'   => get_site_icon_url( 512 ),
      ],
    ],
    'mainEntityOfPage' => [
      '@type' => 'WebPage',
      '@id'   => get_permalink(),
    ],
    'wordCount'       => str_word_count( get_the_content() ),
    'articleSection'  => implode( ', ', wp_list_pluck(
      get_the_category(), 'name'
    )),
    'keywords'        => implode( ', ', wp_list_pluck(
      get_the_tags() ?: [], 'name'
    )),
    'inLanguage'      => get_bloginfo( 'language' ),
  ];

  echo '<script type="application/ld+json">' . wp_json_encode( $schema ) . '</script>';
}
add_action( 'wp_head', 'add_article_schema' );
Enter fullscreen mode Exit fullscreen mode

If you're using the Block Editor (Gutenberg), the get_the_content() function returns the full serialized block content. For word count, filter it through strip_tags( apply_filters( 'the_content', get_the_content() ) ) to get clean text.


The 6 Most Common Article Schema Mistakes

1. headline longer than 110 characters

Google truncates headlines beyond 110 characters and may not display the rich result at all. Keep it tight. If your <title> is longer, write a shorter headline — they don't have to be identical.

// Wrong  130 characters
"headline": "A Very Thorough and Complete Investigation Into the Many Different Ways You Can Optimize Your JavaScript Bundle"

// Correct  68 characters
"headline": "How to Optimize Your JavaScript Bundle: A Practical Guide"
Enter fullscreen mode Exit fullscreen mode

2. Image too small

The single most common reason article images don't show in rich results. If your featured image is 800×450px, it won't qualify. Google requires minimum 1200px width. Always check with the Rich Results Test and look at the image dimensions in the report.

3. datePublished without dateModified

Google uses dateModified as a freshness signal. If you update an article significantly and don't update dateModified, Google may still treat it as the original publish date. Always update both when you make meaningful changes.

"datePublished": "2025-11-01T10:00:00+00:00",
"dateModified": "2026-05-08T14:30:00+00:00"
Enter fullscreen mode Exit fullscreen mode

4. author with only a name and no URL

A name alone is a weak signal. Without an url pointing to an author page, Google can't verify the author entity. This reduces E-E-A-T value significantly for topics where authorship matters.

5. publisher missing the logo

For NewsArticle type specifically, a publisher logo is required for Top Stories eligibility. For BlogPosting it's recommended but not strictly required. Either way, leaving it out is a missed opportunity.

Logo requirements:

  • Rectangular (not square)
  • Max 600×60 pixels
  • Logo must be clearly visible on white background

6. headline not matching the visible <h1>

Google cross-validates your structured data against the visible page content. If your JSON-LD headline says one thing and your <h1> says something different, it's a signal inconsistency that can suppress rich results. They don't need to be character-for-character identical, but they should clearly refer to the same content.


Paywalled Content: The isAccessibleForFree Pattern

If your content is behind a subscription or paywall — even partially — you need to declare it. Presenting paywalled content as fully accessible in structured data is a Google policy violation.

For metered paywall (first N articles free):

{
  "@context": "https://schema.org",
  "@type": "NewsArticle",
  "headline": "Exclusive: Inside the Next Generation of JavaScript Runtimes",
  "isAccessibleForFree": false,
  "hasPart": {
    "@type": "WebPageElement",
    "isAccessibleForFree": false,
    "cssSelector": ".paywall-content"
  }
}
Enter fullscreen mode Exit fullscreen mode

Google will show a paywall indicator in search results and still index your content for ranking — it just won't show the full text in snippets.


Validating Article Schema

Google Rich Results Testsearch.google.com/test/rich-results

Paste your URL or raw JSON-LD. The report will show which rich results your article is eligible for and list every error and warning. Pay attention to the image section — it shows the detected dimensions, which is the fastest way to confirm your image meets the 1200px minimum.

Schema.org Validatorvalidator.schema.org

More thorough than Google's tool. Will catch type errors, unknown properties, and structural issues that Google sometimes ignores.

Google Search Console → Search Appearance → Articles

After deploying and indexing, this shows real crawl data across all your article pages. Errors here directly affect performance. Monitor it 7–14 days after initial deployment and after any schema changes.

Validation checklist before publishing:

  • headline ≤ 110 characters
  • Image ≥ 1200px wide, aspect ratio 16:9 / 4:3 / 1:1
  • datePublished in ISO 8601 format
  • author includes name and url
  • publisher includes name and logo
  • No fields contradict visible page content

Article Schema and Search Console: What to Expect

After deploying Article schema across your blog, here's the realistic timeline:

Days 1–7: Google crawls and reprocesses indexed pages. No visible change in Search Console yet.

Days 7–14: Enhancements tab starts showing data. Expect some warnings — especially around images that don't meet dimension requirements.

Weeks 3–6: Rich results begin appearing in search listings for indexed articles. The first visible signs are usually article images in result snippets.

Month 2+: Meaningful CTR data accumulates. The comparison between pages with and without rich results becomes visible in the Performance report if you filter by article URLs.

Don't expect overnight changes. Structured data's impact on CTR is real but gradual — Google needs time to validate, index, and begin displaying the enhanced results.


Wrapping Up

Article schema won't move you from page 3 to page 1. But if you're already getting traffic, the author E-E-A-T signals are genuinely useful long-term, and getting your article image to display in search results is one of the few direct visual improvements you can make without changing rankings.

A few things to get right before shipping:

  • Right type first: BlogPosting for blog posts, TechArticle for tutorials and docs, NewsArticle only if you're an actual news publisher
  • Image needs to be at least 50K pixels (width × height) — in practice, target 1200×675px and you're safely above it
  • author.name is just the name — no job title, no "Dr.", no "CEO at Company" — put those in jobTitle and worksFor
  • Update dateModified when you make real edits, not just republish with the same date
  • Validate with the Rich Results Test before going live, and then check Search Console 2 weeks later to see how it processed across your actual crawled pages

If you'd rather generate the JSON-LD through a form instead of writing it by hand, there's a free browser-based tool: Article Schema Generator. No signup, everything runs locally.


Working with a schema setup that passes validation but still doesn't produce rich results? Drop the specifics in the comments — there are a few edge cases where that happens and they're usually diagnosable.

Top comments (0)