Connect AI Assistants & Agents to WordPress built an assistant for one person: an authenticated admin, connected through Claude Desktop or Cursor, using an Application Password tied to a real WordPress user account. Every safeguard in that course, permission checks, audit logs, rate limits, assumes that one identifiable person on the other end of the connection. A public chat widget has none of that. The person typing into it arrived from a search engine, has no WordPress account, and never logs in. This lesson is about what changes when the person on the other end isn’t a user at all, just a visitor.
WordPress AI Foundations, specifically a working
wp_ai_client_prompt() setup with a real provider configured. Having read Course 3
helps for contrast but isn’t required, this lesson recaps the relevant parts.
Step 1: the authenticated-admin model, briefly recapped
In Course 3, the trust chain looked like this: a specific person runs Claude Desktop on
their own machine, that client authenticates to your site with an Application Password
generated for their WordPress account, and every ability call that follows is checked
against what that account can do. There’s always exactly one identifiable user on the
other end, and WordPress’s existing permission system, current_user_can(), does most
of the security work for free.
None of that exists for a visitor reading a blog post and clicking a chat bubble in the corner of the page.
Step 2: what’s actually different for a public visitor
| Dimension | Admin-facing agent | Public chat widget |
|---|---|---|
| Who’s talking | One known person, a site admin or editor | Any anonymous visitor, unlimited in number |
| Credential | Application Password tied to a real WP user | None, visitors don’t have WordPress accounts |
| Client | Claude Desktop, Cursor, ChatGPT, on the person’s own machine | JavaScript running in the visitor’s browser, on your page |
| Where the AI call happens | Client-side, the MCP client itself calls the model | Server-side only, inside a WordPress REST callback |
| Permission model | current_user_can() against a real account | No user to check, needs a different kind of gate entirely |
That third row is the one that changes everything downstream. An MCP client like Claude Desktop holds its own copy of whatever credential it authenticates with, and that’s fine because it’s running on a trusted person’s own machine. A chat widget’s JavaScript runs in a stranger’s browser. Anything that JavaScript can read, anyone visiting your site can read, using nothing more than their browser’s developer tools.
Step 3: the rule that makes this safe, the API key never reaches the browser
This is the one principle everything else in this course is built around: your AI provider’s API key must never be sent to, or be readable by, the browser. Not in the page source, not in a JavaScript variable, not in a network request the visitor’s browser makes directly to the provider.
The way this works in practice is a proxy pattern you’ll build over the next two lessons:
- The widget’s JavaScript sends the visitor’s message to a custom WordPress REST
endpoint on your own site, registered with
register_rest_route(). - That endpoint’s PHP callback, running on your server, is the only piece of code that
calls
wp_ai_client_prompt(). It holds the provider credentials because PHP AI Client SDK configuration lives in server-side options, never in anything shipped to the browser. - The callback returns the model’s reply as a plain JSON response. The visitor’s browser only ever talks to your own domain.
A common mistake porting patterns from other stacks is calling an AI provider’s API directly from client-side JavaScript, with an API key embedded in the script or fetched from a “public” config endpoint. Any key readable by the browser is readable by every visitor, and will be scraped and abused within hours of a widget going live publicly. The provider call belongs entirely on the server, behind your own REST endpoint.
Step 4: the abuse surface is different too, and bigger
An internal admin tool is reached by a handful of trusted, known accounts. A public REST
endpoint is reachable by literally anyone with your site’s URL, including bots, scrapers,
and anyone deliberately trying to run up your AI provider bill or extract a system
prompt. There’s no current_user_can() gate available, because there’s no user.
That doesn’t mean the endpoint is unprotected, it means the protections have to be different: a nonce for basic request legitimacy rather than full authentication, and transient-based rate limiting keyed by IP address or a client-generated session ID instead of a user ID. Lessons 3, 4, and 6 build all of this. It also means answer quality matters differently: an admin assistant’s user can tell when it’s wrong, a public visitor often can’t, which is exactly why grounding the widget’s answers in real site content, covered in Grounding AI Answers in a WordPress Knowledge Base, matters more here than it did for Course 3’s internal tools.
Test it: sketch your architecture before writing code
Before Lesson 2, write down the answer to three questions for your own site:
If you can answer all three without mentioning JavaScript touching the provider directly, you’re ready for the next lesson.
Recap
A public chat widget faces an anonymous visitor with no WordPress account, so
Application Passwords and current_user_can() don’t apply the way they did for Course
3’s admin-facing agents. The fix is architectural: the browser only ever talks to your
own custom REST endpoint, and only that endpoint’s server-side PHP callback holds the
credentials to call wp_ai_client_prompt(). The API key never reaches the browser,
full stop. That constraint, plus a public endpoint’s much larger abuse surface, shapes
every lesson that follows.
Resources & further reading
- Adding custom REST API endpoints, developer.wordpress.org
- Connect AI Assistants & Agents to WordPress, the admin-facing model this course contrasts with
- PHP AI Client SDK repository, GitHub