# [WordPress plugin](https://rebilder.com/docs/adapters/wordpress)

> Serve posts, pages and WooCommerce products as markdown from your own WordPress install. Free forever, GPL-2.0-or-later, and silent until you connect it to a Console.

- **Updated:** 2026-08-15
- **Author:** Rebilder
- **Section:** Adapters
- **Description:** Serve posts, pages and WooCommerce products as markdown from your own WordPress install. Free forever, GPL-2.0-or-later, and silent until you connect it to a Console.
- **Publisher:** Rebilder

## What it does

When a request for one of your pages carries `Accept: text/markdown`, the plugin answers with that page’s content as markdown instead of HTML. Same URL, same substance, different format. There is nothing to configure: activate it and it works. Connecting it to a Console is optional and separate. See below.

| Request | Response |
| --- | --- |
| A browser, or anything human-shaped | Untouched. WordPress renders the page. |
| Googlebot or bingbot, **even asking for markdown** | The canonical HTML. Always. |
| `Accept: text/markdown` | That page’s content as markdown, at the same URL |
| A known agent (ChatGPT-User, ClaudeBot, …) | Markdown |
| An unidentified agent that did not ask | HTML, because we never guess a format |
| `GET /llms.txt` | An index of your content |
| `GET /answers` (after an import, plugin 0.2.0+) | Your imported Q&A set: markdown for agents, HTML for people, FAQ data for both |

Posts and pages render with their title, publication and update dates, author, categories and tags, and the body. WooCommerce products render with name, price, availability, stock, SKU, categories and attributes.

## Install

Install and activate the plugin. That is the whole setup. To check it is working:

```
curl -H "Accept: text/markdown" https://your-site.com/your-page/
```

You should get markdown. Loading the same URL in a browser is unchanged. Skip everything else and the plugin never makes a network request; the optional key in Settings → Rebilder is what turns reporting on.

## Connecting it to your Console

> **Silent until you connect it, and only one way out afterwards** With no API key configured the plugin makes no network request at all (no ping, no usage beacon, no version check) and schedules no cron job. When you do connect it, the claim is mechanical rather than rhetorical: **one file** (`includes/class-rebilder-connect.php` is the only file that can make a request), **one function** (`wp_remote_post`), **one address** (`https://api.rebilder.com/v1/events`, hardcoded, no filter and no override). A test walks every shipped PHP file and fails the build on an outbound call anywhere else; another one runs the plugin with no key and asserts zero requests are attempted.

To connect: issue a key in [Console → Settings](/console/settings), paste it into Settings → Rebilder in wp-admin, and press Connect. Saving makes one blocking request to check the key is live, so a typo tells you immediately instead of leaving you with an empty Console. Disconnect in the same place; the key is deleted and the plugin goes back to sending nothing. Uninstall deletes it too.

What is sent: at most one small record per page load, after the page has been delivered, as a non-blocking request whose reply is ignored. The record carries the time, the requested URL **with the query string stripped** except for `page`, `variant`, `sku`, `lang`, `locale` and `currency`, the `Accept` header, which agent it appeared to be (always unverified: the plugin parses the claim and never checks a signature), the referring site as an origin like `https://chatgpt.com` and never the full link, what was served, and the render time in milliseconds.

What is never sent: IP addresses, cookies, anything about a logged-in user, form contents, search terms, order or customer data, and your page content. **Nothing a visitor typed**. The gateway’s query-capture is deliberately not ported, because a fail-closed PII screen running on tens of thousands of sites and drifting looser each release is a privacy bug with our name on it.

Search results pages are dropped entirely, and that is a second mechanism rather than the same one. WordPress writes a search two ways: `?s=<term>`, which the query allowlist removes, and the pretty permalink `/search/<term>/`, which it cannot, because `request.url` keeps the path verbatim and core registers that permastruct itself. So the plugin asks core’s own `is_search()` and returns before an event is built: no event rather than a redacted one. A search results page is not a document any site could have served markdown for, so the miss it would report is the same miss every time.

> **Your traffic mix will read as agent-heavy** The plugin reports agent requests, crawler requests, and human requests an AI assistant referred. **Ordinary human page views are never reported**, because one outbound request per visit is a real cost on shared hosting and not one we want to charge a merchant for data they did not ask us to collect. The consequence is that Console’s human-vs-agent chart has no human denominator for a WordPress-sourced store: read the agent, crawler and Miss panels, not the mix.

The plugin adds no database tables and writes no files. It stores at most two options (the answers payload you paste in, and the API key), each only if you use that feature, and uninstall deletes both along with the rewrite rules for `/llms.txt` and `/answers`. The answers import stays a paste rather than a fetch: a Console key is scoped to `events:write` and cannot read anything.

## Your SEO is untouched

> **Crawlers can never reach the markdown path** Googlebot and bingbot are identified before any other signal is examined, so they receive exactly the HTML a human receives, even if they send a markdown `Accept` header. Showing a search engine something different from a visitor is cloaking, and the plugin is built so that it cannot do it. A test asserts that ordering structurally, not just its outcome.

The detection tables are compared, mechanically, against `@rebilder/agent-detect`, the same tables the hosted gateway uses. Adding a pattern to one and not the other fails the build.

## Caching

> **Your cache must respect `Vary: Accept`** The plugin returns two different bodies for one URL depending on a request header, and sends `Vary: Accept` on every response to keep them apart. If your caching plugin or CDN strips or ignores `Vary`, the first response into the cache is the one everybody gets: one agent’s markdown served to every human, or one human’s HTML served to every agent. Configure it to vary on `Accept` for your pages.

## Nothing is generated

Every value in the markdown is read from WordPress or WooCommerce. No AI model runs at any point. No price is recalculated, rounded, or estimated: WooCommerce’s own stored value is printed as-is with your shop’s currency, and a discount is only shown when WooCommerce says the product is on sale, because comparing the two numbers ourselves would be us deciding what is on sale.

Availability comes from `is_in_stock()`, WooCommerce’s own computed truth, rather than from inventory arithmetic. An agent that disagrees with your Add to Cart button is worse than no agent at all.

## What it does not do

- No Agent Readability Score. Scoring stays server-side in TypeScript: we publish determinism guarantees about it, and a second implementation is a second chance to contradict them. Use [the scanner](/scan) instead.
- No agent traffic log inside wp-admin. The plugin reports to Console when you connect it; it renders no dashboard of its own, and nothing at all until you paste a key.
- No delivery health signal. The send is non-blocking and its reply is never read, so a key revoked later just stops your Console growing. The save-time check catches a bad key at setup, not afterwards.
- Markdown is rendered on request rather than pre-generated on save. It is always fresh and there is nothing to invalidate; for a human visitor the plugin reads two headers and gets out of the way.

Deeply nested markup is worth knowing about: PHP’s HTML parser refuses documents nested more than 256 elements deep, which a page-builder theme can genuinely produce. The plugin detects that case and falls back to the post’s plain text (formatting lost, words kept) rather than serving an empty body.