Paywalled content markup: telling Google what sits behind a paywall
Paywalled content markup tells Google that some or all of a page is available only to subscribers or registered users. You set isAccessibleForFree to false on the article and, with hasPart, point to each locked section by its CSS class.
It exists because of a conflict with Google's rules. Cloaking, showing search engines different content from what visitors get, violates Google's spam policies. A paywalled page that lets Googlebot read the full text while visitors see a teaser looks like cloaking unless the markup explains the difference. If you don't want the locked text indexed at all, you don't need this markup.
JSON-LD example
Copy this into a <script type="application/ld+json"> tag and replace the values with your own.
{
"@context": "https://schema.org",
"@type": "NewsArticle",
"headline": "Inside the county budget: where the new transit money goes",
"datePublished": "2026-09-21T06:00-05:00",
"author": {
"@type": "Person",
"name": "Luis Ortega"
},
"isAccessibleForFree": false,
"hasPart": {
"@type": "WebPageElement",
"isAccessibleForFree": false,
"cssSelector": ".members-only"
}
}
Properties
| Property | Status | What it is |
|---|---|---|
isAccessibleForFree | Required | false when the page or a section needs a subscription or registration. |
hasPart | Recommended | One WebPageElement per locked section, or an array of them. |
hasPart.@type | Recommended | WebPageElement. |
hasPart.cssSelector | Recommended | A class selector such as .members-only that matches the wrapper around the locked section. Only .class selectors are allowed. |
hasPart.isAccessibleForFree | Recommended | false for each locked section. |
Is this for your site?
The markup matters only when two things are true: you want the locked text crawled and indexed, and Googlebot can actually fetch it. That usually means news publishers, subscription newsletters and paid learning sites with a flexible sampling approach.
If your membership plugin removes locked content from the HTML for everyone who isn't logged in, Googlebot sees only the teaser, just like a visitor. Nothing is hidden from users that Google can see, so there is no cloaking concern and the markup adds little. The same goes for content behind a hard login you would rather keep out of search.
Giving Googlebot the full text means recognizing it. User-agent strings are easy to fake, so verify requests the way Google recommends, with a reverse DNS lookup or its published IP ranges; see Googlebot.
Class names, arrays and nesting
Wrap each locked section in an element with a class, and reference that class in cssSelector:
<p>The first two paragraphs are free to everyone.</p>
<div class="members-only">
<p>Subscriber text starts here...</p>
</div>
Several locked blocks on one page become an array of WebPageElement items, each with its own selector. Google's rules are short: only .class selectors, no nesting of locked sections inside other locked sections, and the same markup on every version of the page, AMP and non-AMP included. JSON-LD and microdata are both accepted. The property works on CreativeWork and several subtypes: Article, NewsArticle, Blog, Comment, Course, HowTo, Message, Review and WebPage.
JavaScript paywalls and WordPress membership plugins
Client-side paywalls send the full article in the HTML and hide it with a script or overlay. Google added guidance in August 2025 noting that this design makes it hard for it to work out what is paywalled and what isn't. If you use one, the markup is the clearest signal you can give. The better fix is a paywall that doesn't send locked text to the browser at all.
On WordPress, add the property to the Article markup Hydrogen SEO already prints, using the hydrogen_seo_schema_markup filter and whatever check your membership plugin provides:
add_filter( 'hydrogen_seo_schema_markup', function ( $schema, $post_id, $schema_type ) {
if ( 'Article' === $schema_type && my_site_post_is_members_only( $post_id ) ) {
$schema['isAccessibleForFree'] = false;
$schema['hasPart'] = [
'@type' => 'WebPageElement',
'isAccessibleForFree' => false,
'cssSelector' => '.members-only',
];
}
return $schema;
}, 10, 3 );
Replace my_site_post_is_members_only() with your plugin's own function, and make sure the class in the markup matches the wrapper your plugin outputs.
Controlling snippets and AI previews
Indexing the locked text doesn't mean you want it quoted. Google lists AI Overviews and AI Mode among the features that follow Search's preview controls, so the same tools apply there. Wrap passages in data-nosnippet to keep them out of snippets, or cap snippet length with max-snippet. Test both with URL Inspection in Search Console, which shows how Googlebot renders the page.
Common questions
Does paywall markup create a rich result?
No. It changes nothing visible. It helps Google tell a legitimate paywall from cloaking.
Do I need it if Google can't see my locked content?
Generally no. If Googlebot sees the same teaser as visitors, there is no mismatch to explain.
Can I use an ID selector like #paywall?
No. Google allows only class selectors in cssSelector for this markup.
Does it apply to free content that requires registration?
Yes. Google's guidance covers content behind registration walls as well as paid subscriptions.