Platform guide
The TofuBofu WordPress integration: we publish the post, then read it back
By Arnav Mukherjee, founder of TofuBofu · September 3, 2026
At the end of August we published an article into a customer's site by hand, the way most people do it, and then somebody on our side asked the obvious question. How would the customer ever know whether that landed correctly?
They couldn't. We went and looked at the live page, and it carried a BlogPosting block with an empty headline, the FAQ schema had been injected by JavaScript so the raw HTML never carried it at all, and the block we'd pasted in didn't parse. Three defects, none of them visible in the CMS response, none of them visible to anyone who pastes an article in and moves on. That's the whole argument for publishing through an integration, and WordPress is the destination where we can prove it.
WordPress is the one place we can check our own work
Every other publish integration we run ends at "sent". WordPress lets us read the post back through the REST API with edit context, which returns the raw content after your site has finished sanitising it. So we can compare what we sent against what WordPress actually kept, on your site, rather than on a site we tested against once.
The reason that matters is a WordPress capability called unfiltered_html. A single-site Administrator holds it. On multisite, nobody below a super admin does. So identical code publishing an identical article gets a different answer at two different customers, and any adapter that assumes one answer is wrong roughly half the time.
Three paths come out of that check, and we remember which one your site is on. Embed means your site kept our JSON-LD script block and there's nothing left to do. Handoff means it stripped the wrapper, so we rewrite the body without it and hand you the JSON-LD for your theme template. Auto means we haven't found out yet: publish with it embedded, read the post back, decide from what your CMS actually stored. The decision gets remembered per website, never per post.
One rule doesn't bend anywhere in that logic. A wall of raw JSON must never sit on a customer's live page. We learned that on a Webflow site in August, and the read-back exists so it can't happen twice.
Three fields we refuse to fill
A list of what a tool won't touch tells you more than a list of what it will. We could write three further fields on every WordPress post and we deliberately don't, and each refusal has a reason I'm happy to defend in front of a customer.
No featured image. Our hero graphic is an SVG, and a default WordPress install keeps image/svg+xml out of its allowed upload types, so an upload would bounce on most sites and go through on a minority. Your hero isn't lost, it renders as a figure at the top of the body instead. The line I wrote into the spec still stands: upload the hero, or leave the field and say so, because either is defensible and silence isn't.
No sticky flag. Pinning a post to the front of your blog is an editorial decision about your site, and we aren't the ones qualified to make it on your behalf. No comment_status either, because you already have a site-wide default and sending ours would override it on every single post we publish for you.
What every post does carry, whatever else you configure: the title, the body rendered from markdown to HTML with the hero as a figure at the top, the status, the excerpt, and an explicit slug. That last one is load-bearing. Leave the slug unset and WordPress derives one from the title by rules we don't own, which means our duplicate check and our create call can disagree about the post's name, and the guard that stops a double publish quietly stops doing anything.
SEO on WordPress is plugin-shaped, so we detect rather than assume
There's no such thing as a WordPress SEO title. There's a Yoast SEO title and a Rank Math SEO title, and they're stored differently, so we read your site and find out which one you're running before writing anything.
Rank Math is the easy case: it exposes its title and description as writable REST fields, so we write them and they land. Yoast exposes its own head data as read-only, so we go through registered meta keys, and whether those keys are exposed depends on the site. We say that up front rather than letting the publish path fail quietly. If you run neither plugin, nothing gets written and we tell you so, because a meta key for a plugin you don't have is a value nothing reads and nobody sees.
The same discipline governs where your content lands. Category and tag options come from your own site's taxonomy, read from your WordPress install rather than composed by us, and neither one is ever suggested. Both start empty, because the only way to fill a taxonomy field without asking you is to invent a label, and inventing a label is exactly how a search query ended up in a customer's category field on 31 August. Author works the same way: unset means WordPress credits whoever owns the application password, which is usually not the byline you want on a published article. Anything you leave unset, we don't write.
What happens to your FAQ schema, per site
Connecting it takes about two minutes
There's no OAuth app to register. WordPress ships application passwords in core, and the publish path is a plain POST to the posts endpoint with HTTP basic auth.
In wp-admin, go to Users, then Profile, then Application Passwords. Name it, click Add, copy the generated password. Do that from an Administrator or Editor account, and prefer Administrator on a single site, since that's the role carrying unfiltered_html and therefore the role that decides whether your FAQ schema survives the trip. WordPress displays the password with spaces and accepts it either way.
In TofuBofu, set the brand's domain first, because a connection belongs to a website. One customer with nineteen brands on nineteen domains holds one credential per site, which sounds like overhead until you remember what the alternative did: a review caught one company's article publishing onto a different company's site, reporting success, with no warning anywhere.
Then three fields, site URL, username, application password. Connect validates by asking WordPress who that user is with edit context and checking they hold publish_posts or edit_posts. A user who can sign in but can't create posts gets told exactly that, rather than discovering it on their first publish.
Publish your first deliverable as a draft, then open it in wp-admin and read it. One H1, sane heading hierarchy, a short keyword-bearing slug, the schema either correctly embedded or correctly absent. Move it to published once you've seen that. Ten minutes of paranoia on the first article buys you the confidence to stop looking at the next fifty.
The failures we plan for, and what each one means
404 on /wp-json
The WordPress REST API was not found at that URL. Security plugins and some managed platforms restrict or intercept it, and if that route is shut then nothing else in this integration matters. It is the first gate for a reason.
A 200 that carries HTML
A login wall and an aggressive caching plugin both answer 200 with a page instead of JSON. We treat that as a failure rather than a result. This repo has shipped the read-a-200-as-success defect once already, and once is the budget.
403 on the users list
Not a credential failure. Many sites restrict the user list to administrators, so the author option simply comes back unavailable, the screen says the site did not offer it, and the connection still works. Only a 401 is treated as a bad credential.
A firewall eating the post
A WAF or ModSecurity can 403 a POST whose body contains a script tag before WordPress ever sees the request. Our probe script tells a firewall 403 from a WordPress 403 by whether the body is WordPress's own.
Plain HTTP
Core disables application passwords over an unencrypted connection, and any plugin can switch them off entirely. Both are the site's decision and both are reported as themselves.
A stale mapping
A category deleted since you set it up gets dropped rather than sent, because posting an id the site rejects would fail an entire publish over one term.
An operator without publish rights
Publishing to a customer site is the account owner's action unless that workspace has granted it. An operator working on your behalf can produce and stage content and gets a plain refusal at the publish step.
Where the publish adapter stops and the plugin starts
Be precise about the boundary, because plenty of tools blur it. The publish adapter touches four REST paths: posts, categories, tags and users. It writes no theme header, no site option and no file at your web root. So it can't put Organization schema on every page of your site, and it can't put an llms.txt at your root. Any vendor telling you their WordPress publish integration does site-wide work is describing a plugin, whether or not they mention one.
We ship one. The TofuBofu WordPress plugin hooks wp_head to inject Organization, FAQ and Article JSON-LD, adds a rewrite rule that serves a virtual llms.txt from our API, filters robots.txt to let AI crawlers through, and shows your AI Visibility Index inside wp-admin. It carries its own honest limit too: WordPress can only filter robots.txt while that file is virtual, so a physical robots.txt sitting at your web root beats the filter every time. Two installs, two jobs, and conflating them is how people end up thinking their schema is live when it isn't.
On the llms.txt half, hold your expectations where the evidence puts them. Ahrefs looked at 137,210 domains in May 2026 and found 97% of valid llms.txt files got zero traffic. Of the requests that did arrive, SEO audit tools were the largest single category at 21.7%, and grouping the four AI categories together makes AI bots the largest single bucket at 19.5%. Both groupings belong in the same paragraph or the number gets used dishonestly. Our own crawl corpus reads the same way from the other side: of 39 readable site crawls we measured on 12 August, 38 carried no llms.txt at all, and every one of those 39 sites had run a scan because somebody already suspected they were invisible, so treat it as a sample selected on the problem.
See what the engines say about your WordPress site
A free scan runs your buying questions across six AI engines and ranks the gaps worth writing for.
Get your free auditFrequently asked questions
What credentials does TofuBofu need for WordPress?
Your site URL, a WordPress username and an application password generated from that user's own profile screen under Users, Profile, Application Passwords. No OAuth app to register and no plugin required for publishing. Use an Administrator or Editor account, because the connect check requires the account to hold publish_posts or edit_posts.
Why does TofuBofu not set a featured image on WordPress?
Our hero graphic is an SVG, and a default WordPress install excludes image/svg+xml from allowed upload types, so the upload would be rejected on most sites and succeed on a minority. The hero is not lost: it renders as a figure at the top of the post body. The rule we work to is that you upload the hero or you leave the field and say so, and silence is the one option that is not defensible.
Does my FAQ schema survive when TofuBofu publishes?
It depends on your site, which is why we check rather than assume. WordPress grants unfiltered_html to a single-site Administrator and to nobody on multisite, so an identical publish gets different answers at different sites. We publish with the JSON-LD embedded, read the post back through the REST API, and see whether the script block survived. If it was stripped, we rewrite the body without it and hand you the JSON-LD for your theme template.
Does TofuBofu work with Yoast and Rank Math?
We detect which one your site actually runs rather than guessing. Rank Math exposes rank_math_title and rank_math_description as writable REST fields. Yoast exposes yoast_head_json as read-only, so we write through registered meta keys, which a given site may or may not expose. If you run neither, nothing is written and we say so: the post still carries its excerpt, which most themes render as the description.
Can TofuBofu unpublish a WordPress post?
No, and the adapter declares that rather than guessing. Unpublish clears our record and returns whatever WordPress said, but the post stays on your site until you trash it. A WordPress post can be trashed and a webhook fire cannot be recalled, and what matters is that the product knows which kind of destination it is talking to.
Can the WordPress integration add schema across my whole site?
Not through the publish integration. That adapter touches four REST paths, for posts, categories, tags and users, and writes no theme header, site option or root file. Site-wide work is a separate install: the TofuBofu WordPress plugin hooks wp_head to inject Organization, FAQ and Article JSON-LD, serves llms.txt at your web root through a rewrite rule, and filters robots.txt for AI crawlers. Two different things, deliberately.
Why does TofuBofu send an explicit slug?
Because left unset, WordPress derives a slug from the title by rules we do not own. The duplicate check and the create call could then disagree about what the post is called, and the guard that stops a double publish would silently do nothing. Sending the slug explicitly keeps both sides of that check reading the same value.
Sources and further reading
- WordPress core: application passwords integration guide: how the credential is generated and why it is disabled over plain HTTP.
- WordPress REST API: posts reference: the endpoint every publish goes through, including slug, excerpt and status.
- WordPress: user capabilities: publish_posts, edit_posts and unfiltered_html, the capability that decides your schema's fate.
- Ahrefs: 137K sites and llms.txt: 97% of valid files got zero traffic in May 2026, with both category groupings stated.