Sites
WordPress
In short
Wanigan shows your local WordPress site, names its templates, blocks and patterns, and saves your changes through WordPress’s own REST API as you, with a revision where WordPress keeps one. This page lists everything it covers on WordPress, what each save goes through, and what it refuses and why.
On this page
What it covers
Words on a WordPress page can live in many places: a post’s blocks, a synced pattern, a template or template part in the theme or the database, a navigation menu, a widget, a custom field, a setting, or a theme’s PHP file. Changing them usually means knowing which, and finding the screen that edits it. Wanigan finds that for you and saves through WordPress itself.
| Without the helper | With the helper | |
|---|---|---|
| Finding the site | From wp-config.php, .ddev, .lando.yml or a dev script | The same |
| Naming the parts | Blocks and Elementor elements, which mark themselves; a classic theme’s templates are not named | Every template file, block template and part, block, pattern, widget and menu, through core’s hooks |
| Why a part looks as it does | Only what the page’s own markup says | The filters that shaped it, each with its file, line, time and whether it changed anything, and the queries it ran |
| Saving changes | No | Through core’s REST API, as you, with revisions where WordPress keeps them |
| Moves and Take out | No | Blocks in posts, templates and parts, menu items and widgets, each with Undo |
| Component library | No | Block types, patterns and block styles, pictured |
| The site’s AI | No | Core’s AI Client, its connectors and abilities; what a save runs |
| The site’s palette in Try a style | No: the theme’s tokens and the page’s own colours only | The colours theme.json and global styles declare, by where each came from, from helper version 5 |
The helper names no plugin or theme. Anything built on core shows up because it goes through the template hierarchy, the block API, theme.json, shortcodes, widgets, menus, options and the REST API. A few plugins that keep words where core’s saves cannot reach get an adapter.
Try it on Fieldnotes
Fieldnotes, the WordPress demo, is a made-up field journal with a block theme, synced patterns and a navigation menu. These three steps show the helper, a save and Take out. The full how-to is in the site helper.
Install the helper
Under Layers, choose Set up the WordPress helper…. The plan shows one file,
wp-content/mu-plugins/wanigan-live.php, the line that keeps it out of git, and under what it runs: “Nothing: WordPress loads a must-use plugin by itself.” Press Install the helper, and the page reloads with every part named.The “Set up the WordPress helper” dialog for Fieldnotes: what it adds, the one file it writes in wp-content/mu-plugins, the .git/info/exclude line, and What it runs saying nothing. Picture to come Picture: to come
A screenshot of the real app, with the demo’s data, of what the title above says.
Change a title through WordPress
Open the post “The birch trail after the first hard frost” and select its title. Choose Edit ▾, then the title under In the site · saved by WordPress as you. The sheet says “Checked by WordPress when you save, and kept as a new revision.” Change the words and press Save: the page reloads and Wanigan reads the title back to check the save changed it.
The edit sheet beside the Fieldnotes post title: Edit Title of The birch trail after the first hard frost, the line saying WordPress checks it and keeps a new revision, the title box, and Cancel and Save. Picture to come Picture: to come
A screenshot of the real app, with the demo’s data, of what the title above says.
Take a block out of a template
On the front page, select a block that the Front Page template places and press On a Mac, ⇧X; on Windows and Linux, Shift+X. A template shows on every page that uses it, so Wanigan asks first: “Take … out?”, saying where it shows and what stays, with Take it out. Only the block goes from the markup. Undo puts it back.
Fieldnotes’ front page with a block of the Front Page template chosen and the question “Take … out?”: where it shows, what stays, and Take it out. Picture to come Picture: to come
A screenshot of the real app, with the demo’s data, of what the title above says.
The must-use plugin
The WordPress helper is one must-use plugin file, wanigan-live.php, in wp-content/mu-plugins. A must-use plugin is a plugin file WordPress loads by itself on every request, without activating it, so installing it runs nothing.
- It acts only for requests that carry the token written into it, which only the live view sends. For every other request it only counts content changes, so the view can reload, and changes no output.
- A traced page tells page caches not to keep it, so no cache serves a page with Wanigan’s marks to anyone else.
- It is written in PHP 7.4 syntax throughout, and a test holds it to that: a parse error in a must-use plugin would take the whole site down, on every request.
- Removing it deletes the file, and the
mu-pluginsfolder only if installing made it and nothing else is in it.
What it adds, in the words of its install dialog:
- What made every part of the page, read through WordPress core: the template file and the others the template hierarchy considered, each block and pattern, the filters that shaped its output (each with its file, line, time and whether it changed anything), and the queries it ran.
- Edits titles, excerpts, registered fields, block settings that live in the markup, the site title and tagline, menu links and widgets where they show, through WordPress’s own REST API as the user you are logged in as, saved as revisions where WordPress keeps them.
- Moves blocks in posts, templates and template parts, menu items and widgets by dragging, each with Undo.
- Copies a parent theme’s template into your child theme to override it, only when you ask.
- Go to: the admin menu, Site Editor templates, posts of every type, terms, users and media.
- Reloads the view when content changes in WordPress (an agent running wp-cli, an editor saving), not only when files change.
It also shows one piece of content alone. How to install, update and remove it is in Turning it on.
Tracing through core’s hooks
For a traced page, the helper follows the render through core’s own hooks: the template and the candidates the hierarchy tried, block templates and template parts, every block, the post’s content, titles, shortcodes, widgets and menus. Each filter callback that shaped a part is timed, with whether it changed anything, and the trace adds cost, queries by the file that asked, assets, logs, revisions, and theme.json styles by where each value came from.
Each part says what WordPress stores it as: a post’s content, title or excerpt; a template, template part, widget, menu or menu item; a theme’s PHP template file by its theme and path. A block is known by its place in the stored markup, because WordPress keeps no id for a block, so a moved block’s name stays with the place it left. On Fieldnotes, 164 of 165 parts were named this way, with the same names after a reload.
What it saves, and through what
Each of these is saved through WordPress’s own REST API (the web interface WordPress and its block editor use to read and save content), as the user you are logged in as in the live view. Make it real reaches them from words you type on the page; Edit ▾ › In the site reaches them from a form.
| Words in… | Saved through |
|---|---|
| A post’s title, excerpt or a registered field | The post’s REST route, with a revision. A field keeps revisions only when it was registered to. |
| The site title or tagline | Core’s settings route, by a user who may manage options |
| A block’s words (a paragraph, a heading, a list item, a button) | The REST route of the post, template or part that stores it, with that one block written alone |
| Words a block shows from a post’s field (a block binding) | That field, through the post’s REST route |
| A synced pattern’s own words | /wp/v2/blocks, for every page that shows it, with a revision |
| One placement’s override of a synced pattern | That placement, in the page that holds it |
| A template or template part | /wp/v2/templates or /wp/v2/template-parts. The first save copies the theme’s file into the database, as the Site Editor does; the file itself is never written. |
| A navigation menu’s labels | The menu’s own post, through /wp/v2/navigation |
| A classic menu link’s text | /wp/v2/menu-items, its address sent back as it was |
| A classic widget’s settings | /wp/v2/widgets, which runs the widget’s own update. A widget that does not share its settings with the REST API is sent as WordPress itself encodes and signs it. |
| An image’s alt text | The image block’s own alt, in its markup; or the media library’s alt, through /wp/v2/media, with no revision |
| A category’s or tag’s name, where a menu or an archive heading shows it | Core’s own term save, with its slug, parent and description kept |
| Anything else the post keeps | The stored-words search: the post’s stored content, its excerpt and its custom fields. Elementor’s pages and ACF’s fields go through their own saves. |
Words in a classic post’s content (one written in the classic editor) are looked for by the stored-words search first. Found exactly once, they are saved through core; otherwise they go to an agent with a brief.
Formatted words and nested blocks
A block’s words are kept as HTML. Words with no tag inside are edited as words and sent escaped, so a typed <b> stays words and never becomes markup. Words with inline tags (a link, bold) are edited as their HTML, and the sheet lists the inline tags WordPress keeps.
- New words are spliced in as WordPress itself splices bound rich text: cleaned with
wp_kses_post(), normalized so every tag closes inside its element, inline formatting only, and never a comment. - A block with blocks inside it, such as a list item holding a nested list, has its own words changed up to its first inner block; the nested blocks are kept.
- Only that one block is written: every other byte of the post stays as it was, and the block is read back before the save to check it parses as written.
- From the page, a change must fall inside one run of words between tags. A change across formatting goes to an agent.
Words shown beyond this page
Some words show on more than this page: a template or template part, a synced pattern’s own words, a navigation menu or classic menu link, a widget, the site title or tagline, a media library alt, a term’s name. Each says where it shows before anything is saved.
- In the edit sheet: “Saving it changes …, not only this page.” Save asks once more, “Save it there?”, with Back and Save wherever it shows.
- From Make it real: “These words are kept in …: saving them changes it wherever it shows.” Make it real again within five minutes to save them there.
- A page’s own words, and one placement’s override of a synced pattern, are saved at once.
A synced pattern is a group of blocks saved once and shown on many pages, so its own words change everywhere. Where the pattern lets a page override some of its words, those overrides belong to one placement only.
Classic themes, text domains and child themes
A classic theme writes many of its words in its PHP template files: typed into the HTML (a “Skip to content” link, a footer line), or printed through a translation function. WordPress keeps no stored copy of either. The helper reads each template file the page loaded with PHP’s own tokenizer, never running it, and lists both kinds of words with their line.
- Words typed into a theme’s template go to an agent, with the file and the line.
- A string printed through a translation function goes to an agent with its text domain: the name a theme or plugin gives its translatable strings. A translation file can change it without touching the code.
- A child theme inherits from a parent theme and overrides only some files. When a child theme is active, a parent theme’s template is to be copied to the same path in the child theme and changed there, since a theme update would overwrite the parent.
- A plugin’s or core’s template says whose it is, and is not handed on.
- What a template’s PHP works out is never guessed at. Words in no template are said to be made by a plugin or the theme’s own code (named), by a template’s PHP, or in a template the helper did not read.
Create override… copies a parent theme’s template into your child theme on your word only. It is refused when the file already exists there, when your user may not edit themes, or when the site turns file editing off. A template from a plugin says why a copy would not be used, and a theme with no parent says there is nowhere to copy it.
The reading is bounded: 200 files a page, 256 KB a file, 100 runs of words and 100 strings a file and text domain. A file it did not read says why.
Moves, inserts and Take out
A post’s blocks, a template’s or template part’s blocks, widget areas and menu levels can be reordered and moved, by dragging or with M. Each refuses when what it changes has changed since the page loaded, and answers with the order as it is now.
Inserts place a registered pattern, a block that renders from its settings alone, or, in a menu, a link to the page you are on. A move into a container respects which blocks it allows.
- A block taken out of a post, template or part goes from the markup only: the media, reusable blocks and posts it showed stay. From a post that keeps revisions, it is a new revision.
- A widget goes to Inactive widgets, with its settings.
- A menu item is deleted, and the page it linked to stays. Undo makes it again, with its settings.
- Anything shared (a template, a part, a synced pattern, a menu) says where it shows and asks first. A change to a template is saved in the database, not in your theme’s code.
- The first change to a theme’s template or part makes the Site Editor’s database copy of it. Undoing that change deletes the copy, as the Site Editor’s Reset does.
Seen in the real app on Fieldnotes: a block taken out of a page as a new revision, and out of the Front Page template after its question, each put back with Undo.
The component library
The library (On a Mac, ⇧B; on Windows and Linux, Shift+B) lists the block types, patterns and block styles WordPress has registered, each pictured alone in the site’s own theme at a phone’s and a desktop’s width. Where each is used comes from a read-only search of stored block markup, the theme’s template and part files, patterns and widgets, followed through synced patterns, parts and patterns.
Seen in the real app on Fieldnotes: 166 entries, and a button style found in nineteen places through synced patterns and a template part. A block whose example lives only in its editor script is pictured by that script. More in Components.
The site’s AI, before it spends
A save Wanigan asks a site to make can start AI the site runs, since many AI plugins act when content is saved, and a model call costs someone money. So before and after a save, Wanigan says what AI the site can run, and what ran.
- The Inspector’s AI on this site lists WordPress core’s AI Client (since 7.0), its connectors, each set up or not and where its key comes from (never the key), and the abilities plugins have registered (since 6.9).
- Before an edit, a move or an insert saves, the live view says whose code also runs on that save, from the hooks core fires for it, and whether the site’s AI Client is connected.
- After it saves, it says which providers and models core’s AI Client called, with their token counts, and which abilities ran. Never a prompt, a response or an input.
- Save without the site’s AI Client, in the edit sheet and a move’s question, makes core’s AI Client refuse every prompt for that one save. It stops only AI that goes through core’s AI Client: a plugin that calls a provider on its own is not stopped.
Picture: to come
A screenshot of the real app, with the demo’s data, of what the title above says.
Agents read the same list through Wanigan’s live_site_ai tool. On Fieldnotes, each save’s answer says what core’s AI Client and the abilities did, which was nothing, as no provider is connected there.
What is refused, and why
Every refusal starts with which kind it is. WordPress keeps no such thing means core stores those words nowhere it can write. Wanigan does not do this yet means core could, and Wanigan does not from here; change it in the editor or wp-admin meanwhile.
| Words in… | What to do |
|---|---|
| A static block’s settings, which only its editor script writes into the markup (a heading’s level, say) | Change it in the block editor |
| A pattern the theme renders from its file each time | Change that file, or turn the pattern into the template’s own blocks in the Site Editor |
| A Navigation block with no menu chosen, which builds its links each time | Choose a menu for the block in the Site Editor; then its labels change here |
| A widget registered to display only | Its words are its code’s |
| A block bound to a source a plugin provides | Change it where that plugin keeps it |
| Words a theme’s PHP, a shortcode or a plugin prints | An agent, or a translation file for a translated string |
| Words in… | What to do |
|---|---|
| A post type that is not in the REST API | Change it in wp-admin |
| A widget that is not a standard WordPress widget, or has no simple settings | Appearance › Widgets |
| A synced pattern placed inside another pattern, or overrides kept in an older shape | Open the page in the block editor once |
| A block that shows the post’s or a term’s own data | Change the post or the term |
| A setting read from the block’s markup in a way Wanigan does not write | Change it in the block editor |
| A change across formatting | An agent, from a brief |
Other refusals are about now: your user lacks the capability, the post changed since the page loaded, or someone has it open in the editor. A post locked by anyone, you included, is refused with who holds it, since the open editor would write over the change at its next save.
Not done yet on WordPress
- The newer edits (formatted and nested words, one block written alone, bound words, synced patterns and their overrides, template words, navigation labels, widgets without REST settings, media alt, a term’s name) and a classic theme’s template words are tested under a stand-in WordPress only. None has been saved on a real site yet, and no edited block has yet been opened in the block editor to see it called valid.
- Words typed on the page and made real are tested with stand-in pages and a stand-in site, not yet against a running WordPress.
- The stored-words search, and the Elementor and ACF saves through it, are tested with stand-ins only.
- Multisite, persistent object caches, page caches that answer before WordPress loads, and HTML minifiers have not been checked.
In depth
How the trace hooks core
One callback on WordPress’s all hook counts every hook that fires and lists its callbacks. For the filters that shape a part’s output (the_content, render_block and the rest), it swaps each callback for a wrapper under the same key just before the hook runs. The wrapper calls the original with exactly what WordPress passes, records the time and whether the value changed, and returns the original’s answer; the originals go back as the hook ends.
- Classic themes: each candidate the template hierarchy tried, which exists, and which was chosen; every file
load_template()loads (header, footer, template parts) is a part. The page template itself is a part from WordPress 6.9. - Block themes: the block template and each template part, with whether it is the theme’s file or edited in the Site Editor, and its revisions.
- Blocks: each block is a part from the start of its render to its last filter, with its parent and cost from WordPress 6.6. A synced pattern is one part, labelled with its title.
- The post’s content, its title and excerpt where a template tag prints them, shortcodes, widgets (by their own before and after markup) and menu links (where the walker runs core’s filters).
- Queries are recorded for the traced request only, each with the file and line that asked. A
wp-config.phpthat switches query saving off wins, and the trace says it has none. - Traces are kept for ten minutes, the newest twenty per user. A trace over four megabytes loses its longest lists first, and says so.
- Values whose names look like passwords, secrets, tokens, nonces, keys, cookies, salts, sessions, credentials, signatures or hashes are never shown.
A page cache that answers before WordPress loads serves its page without the helper, so the view knows there is no trace. An HTML minifier that rewrites the page afterwards can strip the marks, and the trace then lists parts the view cannot find.
WordPress versions
The helper names no minimum WordPress version. What needs a newer release says so when it refuses, as “Wanigan does not do this yet”:
| Needs | For |
|---|---|
| WordPress 6.6 | Reading a block’s markup to edit its words |
| WordPress 6.7 | Writing formatted words back into a block |
| WordPress 6.9 | Writing one block alone. Before it, a post’s blocks are written back whole, and only when that changes nothing else. |
| WordPress 6.9 and 7.0 | Listing abilities (6.9), and core’s AI Client and connectors (7.0) |
The trace was written against WordPress 7.1’s source. Releases before 6.9, 6.6 and 6.2 were reasoned from the source, not run. The plugin is kept to PHP 7.4 syntax and checked by a test; PHP 7.4 itself has not been run.
How a block save is checked
- PHP cannot run a block’s editor script, so a change is offered only where the markup itself carries it: a setting of a block that renders on the server with no saved markup, or words the block reads from its own element.
- The stored content must hash as it did when the page was traced, and the block must be the one the page showed.
- Only that block’s bytes are rewritten, its settings only when they changed, and the result must parse back as the block written before it is saved.
- A read-only setting sent changed is refused.
- Validation, sanitizing, capability checks, filtering for users who may not post raw HTML, revisions and the after-save hooks are exactly the block editor’s, since the save goes through the same REST route.
The stored-words search on WordPress
When nothing the page offers holds the words, the helper looks where the post keeps them: its stored content (its markup, a shortcode’s settings and the words it wraps, a block’s settings, changed together with the block’s markup when it keeps them twice), its excerpt, and every custom field. Revisions and autosaves are never searched.
It saves the content with core’s own post save, as a revision, only when core’s filters would store exactly those bytes, or a custom field with core’s metadata save, as a user who may edit the post and while nobody holds it open. A custom field keeps no revision, so Undo for ten minutes is its only way back.
Elementor’s pages and ACF’s fields are saved through their own saves, and copies that never render are never saved into: see Plugin and builder adapters. How words are matched is in Make real.
