How it works Examples Docs Pricing Blog WP Blocks Install free

Blog /

Add a chatbot to a WordPress site without trusting a SaaS

If you searched “WordPress chatbot plugin” any time in the last two years, you found roughly five product categories:

  1. OpenAI-key chatbots. Plugin asks for your OpenAI key, makes calls on your behalf, shows the chat. (See Friday’s post on why this is structurally wrong.)
  2. Vendor-hosted chatbots (Drift, Intercom, Tidio, HubSpot Chat). Embeds a third-party widget on your site. The conversation, the leads, and the analytics live in someone else’s database.
  3. WhatsApp/SMS chatbots. Bolt-ons that route conversations through Twilio or a WhatsApp Business API. Powerful but heavy.
  4. Custom GPT embeds. A <iframe> to a ChatGPT custom GPT. Free, gives the conversation to OpenAI.
  5. DIY, where you read four blog posts and then write the chatbot yourself in PHP, which is the path most agencies avoid.

There is a sixth path that did not exist 18 months ago: a DesignSetGo App that uses your site’s WordPress 7.0 Connector for inference and stores conversations as posts in your own database. The data never leaves the site. The model is whatever your site is configured to use. The billing is whatever your Connector is set up for.

This post walks that build. About 90 lines of code, an afternoon to get right, and the chatbot stays yours.

What you are building

A chat widget in the bottom-right of your site (or embedded as a Gutenberg block in a specific page). When the visitor types a message:

  1. The app calls dsgo.ai.prompt() with the conversation history.
  2. The Connector handles the inference (your site decides which model: Anthropic, OpenAI, your internal LLM, etc.).
  3. The response renders in the chat.
  4. The conversation is saved as a post in a chat_log custom post type, scoped to the visitor’s WordPress user (if logged in) or their session.

You own the data. You can audit it in wp-admin. You can export it. You can delete it. You can analyze it. It is in your database.

Step 1: scaffold

npx designsetgo apps init chatbot
cd chatbot

Open in Claude Code. The prompt:

Build a chatbot widget that lives in the bottom-right corner of any page it is embedded on. A floating “Chat” button that opens a small chat panel. Inside the panel, a message history (visitor messages right-aligned, bot messages left-aligned), and an input field at the bottom. On submit, call dsgo.ai.prompt with the conversation history and a system prompt that says “You are a helpful assistant for [Site Name]. Be concise and friendly.” Render the streaming response into the chat. On every message exchange, append to dsgo.storage.user.* for logged-in users so the conversation persists across sessions; for guests, in-memory only. Style it like a modern site chat: rounded panel, clean typography, accent color matches the site’s WordPress theme primary color (read it from dsgo.site.info()).

Claude Code writes the bundle. The shape is roughly:

import { dsgo } from '@designsetgo/app-client';
await dsgo.ready;

const site = await dsgo.site.info();
const me = await dsgo.user.current();
const history: { role: 'user' | 'assistant'; content: string }[] =
  (me ? (await dsgo.storage.user.get('chat_history')) : null) ?? [];

async function send(message: string) {
  history.push({ role: 'user', content: message });
  renderHistory();

  const stream = dsgo.ai.prompt({
    system: `You are a helpful assistant for ${site.name}. Be concise and friendly.`,
    messages: history,
    stream: true,
  });

  let assistantMsg = '';
  for await (const chunk of stream) {
    assistantMsg += chunk.delta;
    renderStreaming(assistantMsg);
  }

  history.push({ role: 'assistant', content: assistantMsg });
  renderHistory();

  if (me) {
    await dsgo.storage.user.set('chat_history', history.slice(-50));
  }
}

Step 2: the manifest

{
  "manifest_version": 1,
  "id": "chatbot",
  "name": "Site Chat",
  "version": "0.1.0",
  "entry": "index.html",
  "isolation": "iframe",
  "display": { "modes": ["block", "site-overlay"], "default": "site-overlay" },
  "permissions": {
    "read": ["user", "site"],
    "ai": ["prompt"]
  },
  "runtime": { "sandbox": "strict", "external_origins": [] }
}

Three things to notice.

display.modes includes "site-overlay". That mode renders the app as a fixed-position overlay on every page of the site (or every page that matches a configured rule). It is the right shape for chatbots, cookie consent banners, and announcement bars.

permissions.read includes "user" and "site". user for logged-in identity (so chat persists per-user). site for the site name and theme color (so the chat looks like part of the site).

permissions.ai includes "prompt". This is the bridge permission that lets the app call dsgo.ai.prompt(). Without it, the call returns a permission_denied error. The install dialog will say “This app wants to: send AI prompts (through your site’s configured Connector)” so the user understands what they are approving.

Step 3: deploy and test

npx designsetgo apps deploy

If your site does not already have a Connector configured (covered in detail in the App secrets post), you will see the chatbot render but every message will fail with “No AI Connector configured.” Set up a Connector under Settings → Connectors in wp-admin, paste an API key (Anthropic, OpenAI, or whatever you use), save, and try again.

The chat now works. Type “What do you know about my site?” The model gets the system prompt with your site name and answers. Reload the page (if logged in). The history is restored.

Step 4: log conversations as posts

This is the optional but powerful step. Up to here, conversations live in user storage, which is fine for “let the visitor see their history.” For you to audit conversations later, log them as posts.

Add to the bundle:

async function logConversation(history: typeof history) {
  if (!me) return; // skip for guests, or log under an anonymous user
  await dsgo.posts.create({
    type: 'chat_log',
    title: `Chat from ${me.name} on ${new Date().toISOString().slice(0, 10)}`,
    content: history.map(m => `**${m.role}:** ${m.content}`).join('\n\n'),
    status: 'private',
    meta: { user_id: me.id },
  });
}

You’ll need a chat_log custom post type registered with CPT UI (or a small theme plugin). Mark it public: false, show_in_rest: true, show_in_menu: true so it shows in wp-admin but not on the front-end.

You’ll also need permissions.write: ["posts"] in the manifest, which is a Pro feature (write APIs are not in the free tier; the write surface is part of the Pro write story).

After this, every conversation ends with a logged post you can browse in wp-admin. The data is yours. Compliance is your call. Export is wp db export. Deletion is Delete Post.

What this beats

The named alternatives:

Vendor-hosted chatbots (Drift, Intercom, Tidio). The conversation lives in their database. Their privacy policy applies. Their billing applies. Their analytics are theirs to share or sell. Each one is a third-party SaaS subscription.

OpenAI-key chatbots (most of the “WordPress chatbot plugin” results). The plugin holds your key. The key is in wp_options (sometimes encrypted, sometimes not). If the plugin gets compromised, the key gets exfiltrated. The plugin author has to manage commercial terms with OpenAI.

ChatGPT custom GPT embeds. Iframe to ChatGPT. Free, but the conversation is OpenAI’s, not yours. Your visitors’ chat is in OpenAI’s logs.

The DesignSetGo version: the Connector is on your WordPress install. The model your site uses is your choice. The conversation lives in your database. The visitor’s data does not leave your domain except for the inference round-trip to whichever model the Connector is configured for, and that round-trip is governed by your contract with the model provider, not by a third party’s terms.

What this does not solve

A chatbot is a UX problem as much as a technical problem. The above gives you a working chatbot. It does not give you:

  • A knowledge base for the bot to draw from. For that, you need retrieval (RAG) against your site content. The bridge gives you dsgo.posts.list() and dsgo.ai.prompt(); combining them into a RAG pipeline is doable and is a separate tutorial worth writing.
  • A handoff to a human. The chatbot replies; if the visitor needs a human, you should have a clear escalation path. The simplest is a “Talk to a human” button that opens an email composer or a contact form.
  • Rate limiting. The bridge inherits the Connector’s rate limits. If you expect heavy use, watch the 30k ITPM cliff post for the Anthropic-specific gotchas.

The shorter version

A DesignSetGo App with dsgo.ai.prompt() plus the Connector your site is already configured for plus dsgo.storage.user.* plus an optional chat_log CPT gives you a chatbot whose data lives in your database. No third-party SaaS, no API key in your plugin, no vendor analytics on your visitors.

About 90 lines of code, an afternoon to ship, the conversation stays yours.

Further reading