There is a particular kind of dead end you hit with an AI agent on a WordPress site. The agent can see everything. It reads your pages, lists your plugins, tells you what your theme is. Then you ask it to change the hero section, and it cannot, because the page was not built with anything it knows how to touch.
BeTheme is one of the most-sold WordPress themes ever made, and it is exactly that kind of site. It ships its own page builder, BeBuilder, which stores its pages in its own format. The Elementor tools do not reach it. The Gutenberg tools do not reach it. And BeTheme’s settings, all 830 of them, live in a single option, which is not something you hand to a language model and hope for the best.
3.14.0 covers both halves.
Theme Options, without the other 796 settings
betheme-read and betheme-write are two tools, eight operations between them. They register only when BeTheme is the active theme.
The settings side goes through a curated 34-key allowlist across five groups: colours, typography, layout, header and footer, and blog. Body background, highlight colour, heading colour, menu links, font families and sizes, site layout, content width, header style and sticky behaviour, footer layout. The things you actually change when you restyle a site.
Everything outside that list is left alone. Ask for a key that is not on it and the tool tells you it skipped it, rather than writing something it does not understand into a theme option that 830 other settings share.
Reads come back with the type and label attached, so an agent can tell a colour from a font size without guessing, and you can ask “what can you change here?” and get a real answer.
BeBuilder pages as a structure, not a blob
A BeBuilder page is three levels deep: sections hold wraps, wraps hold items. A wrap is a column, and its size sets the column width. An item is a widget.
get-page gives you that tree, with each item’s id, type and settings. set-page writes a whole page back. add-section appends one section and leaves the rest alone, which is what you want most of the time.
Before writing anything, two operations tell the agent what is possible. list-item-types lists every builder item the installed theme registers, 135 on the version this was built against. get-item-schema returns the fields a given item accepts.
That schema is not a hand-written list that goes stale. It is read from the theme’s own renderer, so it matches whichever version of BeTheme is on the site, including items added by a theme update.
Two things that cost me an afternoon each
Both are in the bundled emcp-betheme agent skill, so your agent starts with them rather than learning them the way I did.
An item’s settings live under attr. Not fields, which is the obvious guess and the one I made. A wrong key saves without complaint and simply never reaches the renderer, so the page looks fine in the database and empty on the screen.
And a wrap or item without a size renders as nothing at all. No error, no warning, no visible element. If a section you just wrote comes back blank, check for a missing size before you check anything else.
Those are the failures worth writing down: the ones where nothing goes wrong, loudly.
A tool that cannot be switched on now says why
On the Tools screen, a greyed-out toggle used to look the same whether you had turned a tool off yourself or it depends on software you do not have installed.
Those are very different things, and the second one reads as “broken”, or worse, as “not included in my plan”. This came in from a live site where the Elementor Addons cards were causing exactly that confusion.
Every card with a dependency now carries a badge naming what is missing, such as “Needs Essential Addons for Elementor”, with a line underneath saying whether it is a plugin that is not active or a theme that is not the active one.
Saved, without the page jumping
The “Settings saved” confirmation now slides in as a toast in the bottom right.
WordPress puts notices in a stack above the page, which shifts the whole screen down at the exact moment you have finished with the top of it and are looking at what you just changed. The toast closes itself after a few seconds and waits while you hover it.
Only that one confirmation moved. Notices that carry buttons or explain page state stay exactly where they are, because a corner that fades out is the wrong place for something you have to act on.
A malware scanner called us infected. It was wrong, and we changed the code anyway.
A customer’s host flagged one of the Backup and Migrate files as a PHP backdoor. It was a false positive, and nothing was compromised.
Here is what it saw. The chunked upload that receives a backup archive has a path that accepts the archive base64-encoded, because some hosts’ firewalls reject the raw bytes: a .emcp file carries SQL and PHP, and that trips mod_security. So the code decodes incoming data and writes it to a file, which is precisely what a real dropper does. No pattern-based scanner can tell those two apart by shape.
What a scanner cannot see is the gate in front of it: a valid one-time nonce, the administrator capability, and a fixed destination inside the plugin’s own protected backups folder.
Being right is not much use when a customer is looking at a red “Your application is infected” banner. So the decoding now happens inside a stream filter as the bytes travel to disk, and the archive contents never exist as a string sitting next to the write that places them. That also drops peak memory on a large chunk, so it earns its place on its own merits. The migration connector got the same treatment, since it does the same job on the destination server and would have been the next site reported.
Reading that code again turned up two real gaps worth more than the false positive:
The upload refused nothing. Any filename an administrator sent was written into the backups folder, while the listing only ever offered .emcp, .zip, .sql and .sql.gz back. Both ends now share one list.
And the connector’s media sync wrote whatever filename the bundle carried, with no type check at all. The bundle is signed, so only a paired source can reach it, but a signed payload is still a payload. It now refuses anything the server might execute, checking every extension in the name so photo.php.jpg is refused as well as photo.php, and refusing dotfiles and typeless names outright.
The connector moves to 1.3.0. One already installed on a live site keeps working, since the signing contract did not change.
Everything else
Three of the bundled agent skills were being rejected when uploaded to Claude Desktop. Their descriptions used a placeholder the upload validator reads as an XML tag, and one description had grown past the 1024-character limit. All nineteen packages upload as shipped now.
BeTheme support is Pro, and betheme-write ships disabled by default. Turn it on under Tools → Themes when you are ready to let an agent change things.
Full detail is in the BeTheme docs and the changelog.
