D
P
0
← All articles Baca dalam Bahasa Indonesia

Technical SEO, Redirects & Migrations

Rank Math JSON-LD `author` Showing an Email Address? Only Custom Post Types Leaked It, Because Pages Got WebPage

· · 7 min read
Rank Math JSON-LD `author` Showing an Email Address? Only Custom Post Types Leaked It, Because Pages Got WebPage

The email address sitting in the JSON-LD author field belonged to my own WordPress account. The client's SEO lead was the one who found it, in an email showing one of the service pages, whose author node carried that address in its name field.

The SEO lead's rule, quoted verbatim with the brand owner's name masked:

"For [the brand owner], we use other users only on some blog pages. For the rest, [the brand owner] should be the only name in all schemas."

So the problem was bigger than an exposed address. A service page is not a blog page, which means that under this rule my name should not have been in its schema at all, email or not.

On that site my account was recorded as the author of at least 24 entries, namely 15 custom post type items plus 9 plain pages. Only the 15 custom post type items leaked. The 9 plain pages were clean.

The schema type decides, not the author column

On this site Rank Math emitted an Article schema for custom post types, and Article carries an author. A plain page got a WebPage instead, and that WebPage node carried no author at all. That is why only the custom post type items leaked a name.

// Simplified. The email address is masked.
 
// custom post type item: an Article node, carrying author
"@type": "Article",
"author": { "name": "<my account's email address>" }
 
// plain page: a WebPage node, no author
"@type": "WebPage"

The clearest example sat in the framework section. The framework post type deliberately sets has_archive => false so its hub can be a designed page rather than a generic archive list, which makes that hub a real Page. The hub was clean even though my account was its author. The five framework items under that hub's URL, also under my account, are custom post type items, and all five carried the email address. To a visitor it is one section of the site. To Rank Math it is two different schema types.

Do not assume this mapping holds on other sites. Rank Math picks a default schema type per post type through the Schema Type setting in Titles & Meta, and a single post can carry its own schema too, so on another install a page may well get Article. For this site, a note from 18 August records Rank Math PRO 3.0.76 as installed. A wp-admin check on 6 August recorded the Schema module as on, and an audit the same day of every rank_math/* filter in the theme found none that touched schema, including no rank_math/json_ld. A live check that day also recorded Organization and WebSite on the home page, Article and BreadcrumbList on a book page, and BlogPosting and FAQPage on an article. That book page is one of the custom post type items discussed here.

The same logic explains why the 46 pages authored by yet another account did not break the client's rule either. Read the rule as "the author column must be the brand owner" and 9 plus 46, so 55 pages, would look like violations, even though not one of them emits an author name in its schema. So I did not reassign a single page author. What gets checked is the schema type each URL actually emits.

Fifteen items, while the menu only reached five

All 15 custom post type items were under my account, with no mixed ownership: 4 books, 5 frameworks, 3 products, and 3 services. Only 5 of them were reachable from the header menu the SEO lead named: the 3 services, plus 2 other items reached through 301 redirects from two other URLs. I found the other 10 by query and fixed them as well, because the same complaint would have come back on a book or framework page.

The target account needed care too. There were two user accounts carrying the brand owner's name: the main one with 516 items and its own author archive, and a duplicate with 18 items and an ugly nicename. The main one was used.

Reassigning the author without bumping post_modified

I moved the author with a direct SQL UPDATE post_author rather than wp post update, and that was deliberate: it keeps post_modified from moving, so the schema's dateModified and the sitemap's lastmod stay honest. wp post update goes through wp_update_post(), which ends up calling wp_insert_post(), and that function sets post_modified to the current time on every update. Changing the author does not change any item's content, so there was no reason for those 15 items to look freshly edited.

-- Simplified: table prefix, post type names, and account IDs replaced with placeholders.
UPDATE wp_posts
SET    post_author = @owner_id
WHERE  post_author = @my_id
  AND  post_type IN ('cpt_book', 'cpt_framework', 'cpt_product', 'cpt_service');
 
-- Revert: the same statement with the two IDs swapped.
UPDATE wp_posts
SET    post_author = @my_id
WHERE  post_author = @owner_id
  AND  post_type IN ('cpt_book', 'cpt_framework', 'cpt_product', 'cpt_service');

The revert really is that simple: the same statement with the two IDs swapped, scoped to those four post types. One condition makes it valid. Before the change, no item in those four post types may already belong to the target account, because any such item would be dragged over to my account when the revert runs. Here the condition held, because ownership was not mixed: all fifteen items belonged to my account.

A direct UPDATE also skips every WordPress save hook, so nothing clears any cache automatically. Afterwards I ran wp cache flush, plus rocket_clean_files() aimed at just those 15 URLs. No Cloudflare purge was needed, because the edge came back clean on its own.

Verifying at the origin

I checked all 15 URLs at the origin with a browser user agent. Every one answered 200, the email string appeared zero times, and the author node now points at the brand owner's main account.

// After the fix. Simplified, identifiers masked.
"author": { "@id": ".../author/<owner-account>/", "name": "<brand owner's name>" }

That node also carries the brand owner's real photo and a worksFor pointing at #organization. In my notes the complaint is marked closed on 2 September. What I hold is my own verification; any response from the client after the fix is not recorded there.

A display_name that was an email address

Separate from the complaint, one thing made the leak look far worse than it needed to: my account's display_name was literally that email address. Rank Math fills the name in the author node from the author's display_name, so what showed up in the schema was a full email address, not merely a name in the wrong place.

That display_name now holds my real name, so whatever gets recorded under that account later can no longer surface the address as a name. What has not changed is user_nicename. It is still email-shaped, so the author archive URL keeps the email shape too. That archive is noindex and now holds zero posts.

My notes do not say how that display_name came to hold an email. What is worth knowing in general: when an account is created without a first and last name, WordPress sets display_name to the user_login, and user_nicename is derived from the user_login as well. An account whose login is an email address can end up with both shaped like an email.

Before reassigning authors on any other site

The client's rule was about names in schema, not about the author column in wp-admin, so the first check belongs on the URL's output: which schema type comes out, and whether that type carries an author. That shows which class of items has the problem, and the fix can then cover the whole class instead of only the URLs that happened to be in the complaint. If duplicate accounts share a name, confirm the target account first. If authors have to move without making items look freshly edited, use a path that does not touch post_modified, and write the revert before the first statement runs.

Finally, check display_name and user_nicename on every account that might be recorded as an author. Rank Math uses the first as the name in the author node. The second builds the author archive URL, and that URL serves as the author node's @id as long as author archives are not disabled.