WP AI Edit
AI chat in the WordPress admin that edits the site – English edition (client side of the WP AI Edit / WP Agency Edit pair).
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/livedialai/wp-ai-edit_en/archive/refs/heads/main.zipThe current version, with its version number, is available under Releases.
AI chat in the WordPress admin that edits the site. It appears as a floating widget in the bottom right of wp-admin — it does not exist on the public site.
Remote Access (Agencies)
An agency instance can operate this site remotely over HTTPS. Access runs through application passwords — a WordPress core feature, not a key system of its own. To WordPress, the caller is a regular user, so all permission checks apply unchanged: an administrator credential has all rights, an editor correspondingly fewer.
Create a credential: Settings → WP AI Edit → Remote Access. The password is shown once — as is standard in WordPress. Any credential can be revoked there at any time; revocation takes effect immediately.
What the agency can query:
GET /wp-json/wp-ai-edit/v1/auskunft
identifier, site, WordPress and PHP version, theme, page builder,
active plugins with versions, number of abilities, permissions of the credential
GET /wp-json/wp-abilities/v1/abilities
POST /wp-json/wp-abilities/v1/abilities/kiedit/get-page/run
Run each ability individually (the standard WordPress way for the Abilities API)
Log: Every login and every call is recorded with time, credential name,
route, status, and truncated IP in the log under Settings → Remote Access.
Suggestions submitted via remote access additionally carry the
origin fern.
Remote access can be turned off in the Remote Access tab. Failed login attempts are logged as well.
What it can do
| Area | Ability |
|---|---|
| Filling pages | create-page, update-page with block markup |
| Settings | set-options (title, tagline, posts per page …) |
| Plugins | install-plugin, toggle-plugin, set-plugin-setting, list-plugins |
| Design templates | fetch-design – reads external pages and extracts colors, fonts, layout |
| Safety | snapshot before every change, rollback to revert |
| Reading | inspect-site – identity, theme, pages, menus, plugins; get-page for the full page content |
| Small changes | replace-text – replaces one passage of text, everything else stays untouched |
| Images | generate-image – create an image from a description, put it in the Media Library |
| Suggestions | list-pending, apply-pending, discard-pending |
Structure
wp-ai-edit.php Main file: hooks, admin-only, settings
includes/class-abilities.php 18 abilities via the WordPress Abilities API
includes/class-rest.php REST routes /chat /reset /status /pending
includes/class-inspector.php Design extraction from external pages
includes/class-workflow.php Suggestions, preview, apply, discard
includes/class-bild.php Image generation via WaveSpeed
includes/class-llm.php OpenAI-compatible client with tool calls
assets/js/widget.js Chat UI, suggestion cards, image preview
assets/css/widget.css Speech bubble, panel, cards
prompts/chat.md System prompt read-only mode
prompts/editsite.md System prompt editing mode
docs/funktionen.md generated reference of all functions
docs/referenz-erheben.php Collection script
docs/referenz-erzeugen.py Generator of the reference
Documentation
| File | Content |
|---|---|
README.md |
Overview, installation, concept, decisions, test results |
docs/funktionen.md |
Complete reference: all 18 abilities with parameters, types, and required fields; REST routes; every setting with its default; database options; all classes with their public methods; hooks; commands; page-builder detection |
docs/referenz-erheben.php |
Collection script, runs in the installation |
docs/referenz-erzeugen.py |
Generates docs/funktionen.md from it |
Regenerating the reference
docs/funktionen.md is not written by hand. The data comes from what
WordPress actually registers — so that the reference does not go stale while the code
moves on:
# 1. Collect the inventory in the installation
cp docs/referenz-erheben.php /tmp/
wp eval-file /tmp/referenz-erheben.php # writes /tmp/wpaie-doku.json
# 2. Download the JSON and generate the reference
python3 docs/referenz-erzeugen.py <plugin-directory> <path-to-wpaie-doku.json>
The collection script stores its output via the environment variable WPAIE_DATEN,
the generator script its target path via WPAIE_ZIEL. Keys are replaced with
(gesetzt) in the process — the reference never contains credentials.
Why it does not exist on the site
add_action( 'admin_enqueue_scripts', … ); // admin only
add_action( 'admin_footer', … ); // markup only in the admin
register_rest_route( …, [ 'permission_callback' => 'nur_backend' ] );
No wp_enqueue_scripts, no wp_ajax_nopriv_*, no output in the frontend.
Calling the REST route without logging in returns HTTP 401.
Chat commands
| Command | Effect |
|---|---|
/editsite |
Editing mode: all abilities, editing prompt |
/normal |
Back to read-only mode |
/inspect |
Output the site state as JSON |
/reset |
Clear the conversation history |
/hilfe |
Command overview |
Keyboard shortcuts: Ctrl/Cmd + K opens the widget, Esc closes it.
Suggesting and confirming
By default the agent changes nothing directly. A write operation is stored as a suggestion and appears in the chat as a card with three buttons:
- View preview — opens the suggested version under
?wpaie_vorschau=<id>. For logged-in users only; the live page stays untouched. - Go live — applies the change, creating a backup beforehand.
- Discard — deletes the suggestion, nothing happens.
The agent asks when it is not clear what is wanted:
Should I put this live right away — or would you like to look at it first?
The workflow can be changed under Settings → WP AI Edit → Workflow:
- Suggest first (default) — every change is presented
- Apply immediately — changes go live directly; backup and rollback remain
Regardless, the user can say "do it directly" in the chat. Then the
model sets direkt: true and that one change is applied without a clarifying
question. Conversely, the agent asks first for large rebuilds.
What happens behind the scenes
User: "Change the opening hours to 9-18"
↓
kiedit/update-page (without direkt=true)
↓ nothing written
Suggestion 2b2c552b902f + preview link
↓ user clicks "View preview"
?wpaie_vorschau=2b2c552b902f → 403 for outsiders, view for logged-in users
↓ user clicks "Go live"
Backup → wp_update_post() → live
The agent can also manage suggestions itself:
kiedit/list-pending— open suggestions with IDskiedit/apply-pending— apply (only after consent)kiedit/discard-pending— discard
Abilities
| Ability | Effect |
|---|---|
kiedit/inspect-site |
Title, theme, pages, menus, plugin state |
kiedit/list-plugins |
Installed plugins and state |
kiedit/fetch-design |
Read an external page: colors, fonts, layout |
kiedit/create-page |
Create a page (suggestion or direct) |
kiedit/update-page |
Change a page (suggestion or direct) |
kiedit/set-options |
Core options from a whitelist |
kiedit/install-plugin |
Install a plugin from the repository |
kiedit/toggle-plugin |
Activate/deactivate a plugin |
kiedit/set-plugin-setting |
Set a plugin setting |
kiedit/snapshot |
Backup of a state |
kiedit/rollback |
Restore from a backup |
kiedit/list-pending |
Open suggestions |
kiedit/apply-pending |
Apply a suggestion |
kiedit/discard-pending |
Discard a suggestion |
kiedit/get-page |
Read a page's title, status, and full block content |
kiedit/replace-text |
Replace a single passage of text, everything else stays |
kiedit/generate-image |
Create an image from a description, put it in the Media Library |
kiedit/image-status |
Fetch a running image job |
Reading pages and changing them individually
The most important principle: update-page replaces the complete page
content. Without knowing the current text first, a write attempt would empty the
page. That is why the prompt insists: get-page first, then write.
kiedit/get-page → title, status, link, full block content, character count,
block count, and whether the page is editable through
WordPress at all
For typical customer requests — opening hours, prices, phone number, a word in the
text — replace-text is the right tool:
{ "id": 61, "suchen": "Our Menu", "ersetzen": "Menu 2026" }
It counts the occurrences, replaces only the first one by default, and leaves everything else untouched. If it cannot find the passage, it stops with a clear notice instead of writing somewhere random. In the test: 1373 → 1371 characters, because the new text was two characters shorter — and Tiramisù, Pizza Marinara, and the rest of the content stayed unchanged.
Both paths go through the suggestion mechanism, not directly to the page.
Pages with Elementor, Divi, and other page builders
Page builders do not store their content in the WordPress content, but in their own fields. A write attempt there would have no effect: the database change happens, but the page looks unchanged.
The plugin detects this and refuses to work instead of failing silently —
get-page reports in the bearbeitbar field which page builder is involved:
| Page builder | Detection marker |
|---|---|
| Elementor | _elementor_edit_mode = builder |
| Divi | _et_pb_use_builder = on |
| WPBakery / Visual Composer | _wpb_vc_js_status = true |
| Bricks | _bricks_page_content_2 filled |
| Oxygen | ct_builder_shortcodes filled |
update-page and replace-text then return an error in plain text, and the agent
tells the user that this page can only be changed in the page builder itself.
Image generation
Under Settings → WP AI Edit → Image Generation, service, key,
model, and size can be entered freely. The default is WaveSpeed with
bytedance/seedream-v4.5 (Seedream 4.5, text to image) at 2048×2048.
User: "Take a photo of our pizza for the menu"
↓
kiedit/generate-image → job to WaveSpeed
↓ ~15–25 seconds
Image URL → download → Media Library (attachment ID)
↓
Agent shows the URL and adds it to the page via a suggestion
The key is stored exclusively in the WordPress option and is sent only to the
configured service. Other WaveSpeed models can be entered directly, for example
bytedance/seedream-v5.0-pro, bytedance/seedream-v4, or
wavespeed-ai/z-image/turbo.
Requirements
- WordPress 6.9+ (Abilities API; tested on 7.1)
- PHP 8.0+
- An OpenAI-compatible API with tool calls. Base URL, model, and key are configured in the plugin, not in core.
Default: DeepSeek — base URL https://api.deepseek.com, model deepseek-flash.
Freely changeable, works just as well with OpenAI (https://api.openai.com/v1, gpt-4o),
Mistral, a custom gateway, or local Ollama.
Installation
From the release (the easy way)
Ready-made package under Releases — it extracts to wp-ai-edit/ and can be
uploaded directly. This link stays valid and always points to the latest version:
https://github.com/livedialai/wp-ai-edit_en/releases/latest/download/wp-ai-edit.zip
The same file is also available next to it with the version number in the name
(wp-ai-edit-<version>.zip) if you want to pin a specific version.
WordPress → Plugins → Add New → Upload Plugin → select the ZIP → activate.
From the source code
# Copy the plugin folder to wp-content/plugins
wp plugin activate wp-ai-edit
A word of caution about the source ZIP
GitHub's automatic archive (/archive/refs/heads/main.zip) extracts to
wp-ai-edit-main/. WordPress would then install the plugin under that name —
it works, but the directory is named differently than expected. If you want to
avoid that, use the release package or rename it beforehand.
From a running installation
wp plugin install https://github.com/livedialai/wp-ai-edit_en/releases/latest/download/wp-ai-edit.zip --force
Then under Settings → WP AI Edit:
| Field | Meaning |
|---|---|
| Base URL | without /chat/completions; the path is appended |
| Model | must support tool calls |
| API key | displayed masked; leave empty to keep the old one |
| Temperature · max tool rounds · timeout | freely adjustable |
| Thinking mode | turns on the model's thinking |
| Test connection | sends a test request and shows the response |
Below that, the two system prompts as editable text fields. They are pre-filled with a complete Gutenberg reference (block markup, block types, structure) and can be changed directly in the admin. Clearing a field and saving restores the bundled version.
Finally, visibility: at which capability level the widget appears.
How the AI integration works
The plugin talks to /chat/completions in OpenAI format itself — deliberately not
through the core AI client, so that base URL, model, and key are freely selectable.
// Tool definitions from the registered abilities
$tools = WP_AI_Edit_LLM::werkzeuge( $ability_names );
// Loop: model responds with tool_calls -> execute ability -> return result
$antwort = WP_AI_Edit_LLM::chat( $messages, $ability_names );
Sequence per round:
POST {base_url}/chat/completionswithtoolsandtool_choice: auto- If the response contains
tool_calls, every call is executed via$ability->check_permissions()and$ability->execute() - The result goes back into the conversation as
role: tool - Repeat until a text response or
max_runden
Ability names are rewritten for the API: kiedit/inspect-site →
kiedit_inspect_site, with reverse mapping.
Permissions per mode: in the chat only read-only abilities, in editing mode all.
An editor can use the same chat and automatically gets only what their
role allows — the check happens per ability in permission_callback.
Design templates from external sites
kiedit/fetch-design loads a public URL and returns compact JSON:
{
"titel": "…",
"farben": [ {"wert": "#0a0a0a", "anzahl": 42} ],
"css_variablen": { "--brand-pink": "335 100% 50%" },
"schriften": ["Space Grotesk", "Inter"],
"google_fonts": ["Space Grotesk"],
"struktur": { "header": 1, "nav": 1, "section": 9, "footer": 1, "grid": 7, "flex": 33, "karten": 19 },
"ueberschriften": ["H1: …", "H2: …"],
"cta": ["Start for free →"],
"bilder_alt": ["…"]
}
Private and reserved IP ranges are blocked so that the server cannot be used as a probe into the internal network.
Tested
test.pizzafamily.site (WP 7.1, PHP 8.2):
| Check | Result |
|---|---|
| 18 abilities registered | ✅ |
Connection to DeepSeek deepseek-flash |
✅ responds |
| Tool loop (model calls ability, result comes back) | ✅ |
inspect-site, fetch-design on gofonia.de |
✅ colors, fonts, layout |
create-page with Gutenberg markup |
✅ 12 blocks, parsed without errors |
Write attempt without direkt=true |
✅ suggestion only, page unchanged |
| Preview link without login | ✅ HTTP 403 |
| Preview link with login | ✅ renders the new version |
| Apply suggestion | ✅ content changed, suggestion list empty |
replace-text character-exact |
✅ 1373 → 1371, rest preserved |
generate-image |
✅ 14 s, 2048×2048, attachment in the Media Library |
| Image visible in the chat | ✅ rendered at 413×413 |
| Page-builder detection (5 systems) | ✅ detected, writing refused |
| Permission check without login | ✅ rejected |
starfood.pizza (WP 7.1, PHP 8.4, WooCommerce shop, 89 products):
| Check | Result |
|---|---|
| Plugin installed and active | ✅ |
| 18 abilities registered | ✅ |
| Frontend untouched (home page, /shop/, /warenkorb/) | ✅ HTTP 200, 0 occurrences |
| Widget in the admin | ✅ visible |
| Live question via the chat | ✅ counted 89 dishes, listed 8 active plugins |
Limits
- Not a replacement for a page builder. Elementor, Divi, and relatives build pages visually with instant feedback. A chat is the wrong interface to move a border by three pixels. The tool does not replace the builder, but the call to the agency for small changes to existing pages.
- Page-builder pages cannot be edited (see above). That is intentional.
- Design remains manual work. Structurally correct block markup is not the same as good design. The Gutenberg reference in the prompt is more important than the choice of model.
Technical limits of v1
- The theme on the test installation (
pizzafamily) is not a block theme. Design changes therefore take effect through page content, menus, and theme options. At thetheme.jsonlevel (Global Styles, Templates), the plugin only works with a block theme – the corresponding abilities are missing in v1. - The conversation history is embedded into the prompt as text, not as a
structured message list. For longer sessions,
PromptBuilder::with_history()would be the next step. - No streaming output; the response arrives in one piece.
install-pluginaccepts repository slugs only, not URLs.