WP Manifestindependent plugin directory
manifest / developer / what-renders-this

What Renders This releases

Dev-only WordPress plugin: which template rendered this page, which ACF groups are attached, which partials fired

by Ian Garcia · github.com/quietcactus/what-renders-this · website

0stars
1release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/quietcactus/what-renders-this/releases/download/v1.2.1/what-renders-this.zip

Readme

What Renders This

A dev-only WordPress plugin that answers, on any frontend page: which template file rendered this, what the route and post context are, which ACF field groups are attached, and the ordered chain of theme partials that fired.

  • Version: 1.2.2
  • Author: Ian Garcia
  • License: GPL-2.0-or-later
  • Requires PHP: 7.4+
  • Requires WordPress: 6.0+
  • Requires: nothing. ACF is optional — the ACF section hides itself when ACF is not active.

What it does

Adds an admin bar node labelled What Renders This, with the resolved template in its tooltip. Click it and a panel opens with the full picture:

Section What it shows
Route Which is_* conditionals matched, the queried object class, the object ID, the post type.
Template The resolved template (theme-relative), the _wp_page_template assignment, and every candidate the hierarchy considered with the winner flagged.
ACF Field groups whose location rules match this post, each with its local-JSON file and field list.
Partials Every theme file included during the render, in first-inclusion order.

Copy context puts the whole report on the clipboard as markdown. A BugHerd ticket carries a URL; this turns that URL into a file list before you open an editor, and the blob pastes straight into Claude Code as starting context.

## Page context
URL       https://staging.example.test/team/jane-smith/
Route     is_single · is_singular · post_type=team · ID 2044
Template  mytheme/single-team.php
Assigned  (none — resolved by hierarchy)

## ACF groups
- Team Bio  mytheme/acf-json/group_5f2a.json  (14 fields)
    bio_headline (text)
    bio_body (wysiwyg)

## Partials, first-inclusion order
1. mytheme/includes/include-tracker.php
2. mytheme/includes/include-banner.php
3. mytheme/includes/include-bio-tabs.php

Installation

Grab what-renders-this.zip from the latest release, then either:

  • Upload itPlugins → Add New → Upload Plugin, pick the zip, activate.
  • Unpack it — extract what-renders-this/ into wp-content/plugins/, activate What Renders This in Plugins.

Upgrading from 1.2.0 or earlier, which shipped as wrt-inspector/: install this version, then delete the old wrt-inspector/ folder and reactivate. WordPress treats the renamed directory as a separate plugin, so both will be listed until you remove the old one.

There is nothing to configure. The plugin writes no options, creates no tables, and makes no database writes at all.

Who sees it

Activate it and it works. There is no environment check and nothing to define.

The only gate is manage_options, and that is deliberate: everything the panel shows — template filenames, ACF group names, local-JSON paths, partial paths — is already in front of that user in the dashboard. The plugin makes no database writes, and field values stay off unless asked for per request. An environment gate would have added no protection while silently disabling the plugin on managed hosts, which report production from wp_get_environment_type() for staging and dev installs alike.

It also skips admin screens, AJAX, REST, cron and WP-CLI. v1 is frontend only.

Turning it off

A constant, for a site where you want it installed but dormant:

define('WRT_INSPECTOR', false);

Or a filter, for anything conditional — per environment, per user, per URL:

add_filter('wrt_inspector_enabled', function ($enabled) {
  return wp_get_environment_type() !== 'production';
});

The constant overrides the default in both directions; the filter runs last and wins.

Caching

Whenever the gate passes, the plugin sends nocache_headers() on send_headers. The panel renders inline in the page HTML, so a misconfigured full-page cache could otherwise store an administrator's response and serve the panel to anonymous visitors.

Usage

  • Admin bar node — the label is the plugin name; the tooltip carries the resolved template and the object ID. Click to toggle the panel, Esc to close.
  • ?wrt-inspect=1 — renders the panel already open, independent of the admin bar. Use this when a theme suppresses the bar or a user has turned it off.
  • ?wrt-inspect-values=1 — adds ACF field values to the panel and to the copied report. Off by default, and the values are not fetched server-side at all unless the parameter is present. The show values link in the ACF section toggles it.

Field values are live client content. Turning them on can put names, addresses or unpublished copy into anything you paste the report into. That is why it is opt-in per request rather than a saved preference.

How it works

File Responsibility
what-renders-this.php Bootstrap — constants, wrt_inspector_enabled(), hook wiring on init.
includes/class-wrt-inspector-gate.php On/off switch, capability check, request-type check.
includes/class-wrt-inspector-route.php Captures template_include and every {$type}_template_hierarchy filter; route conditionals.
includes/class-wrt-inspector-trace.php Snapshots get_included_files() and diffs it to produce the partial chain.
includes/class-wrt-inspector-acf.php Resolves attached field groups and locates their local-JSON files.
includes/class-wrt-inspector-panel.php Admin bar node, panel markup, markdown report.
includes/class-wrt-inspector-state.php Composes the above and builds the context array once, at the end of the footer.
assets/inspector.css / inspector.js Namespaced panel styling, toggle and clipboard behaviour.

Why the partial tracer works the way it does

Themes pull partials with raw include(locate_template('includes/include-banner.php')). WordPress fires no action for that, and locate_template() has no filter. There is nothing to hook.

So the tracer works one level below WordPress, at the PHP language level:

  1. Snapshot get_included_files() at template_redirect priority 0.
  2. Snapshot it again at wp_footer priority PHP_INT_MAX.
  3. Subtract, keep only files under the parent or child theme directory, normalise to theme-relative paths.

That catches include, require, get_template_part() and include_module() uniformly, because all four end up in the same PHP include table.

Why the node is added on wp_before_admin_bar_render

The node tooltip carries the resolved template, so the node cannot be built before template_include has fired. Both admin_bar_menu and wp_before_admin_bar_render are safe on that count — they fire back to back inside wp_admin_bar_render(), which runs on wp_body_open (or wp_footer as a fallback), long after the template is known. _wp_admin_bar_init() runs earlier on template_redirect, but it only instantiates WP_Admin_Bar and registers core's callbacks; it does not fire admin_bar_menu.

wp_before_admin_bar_render is used because it runs after every admin_bar_menu callback, which puts the node last in the bar.

Known limits

  • First-inclusion order, not render order. get_included_files() lists each file once. A partial pulled in by six different modules appears a single time, at its first inclusion point. Exact ordering with repeat counts would need a logging wrapper around every include in the theme, which is a theme change and out of scope.
  • The footer is the boundary. Files included after wp_footer are missed. The second snapshot hooks at PHP_INT_MAX so it runs last within the footer; shutdown would be more complete but fires after </html>, where the panel cannot be rendered.
  • Full-page caching. If a staging environment serves a cached page, PHP never runs and the inline panel is whatever was cached. A stale panel is possible, and self-evidently stale.
  • Non-post contexts. Archives, search and 404 have no post ID, so ACF location rules do not apply. The panel says so explicitly rather than rendering an empty section.

Test matrix

Verify against the route types that actually differ:

  • A page with a custom template assigned in the editor.
  • A custom post type single whose template nests partials two or more levels deep.
  • A second custom post type single, to confirm the hierarchy candidate list changes with it.
  • The blog archive.
  • Search results.
  • A 404.
  • The gate on a real WP Engine staging install — the assumption most likely to be wrong.

Development

There is no build step for development — the source in includes/ and assets/ is what runs. dist/ is gitignored; it is a scratch copy of the installable files plus the zip, rebuilt at release time and attached to the GitHub Release:

./build.sh
gh release create "v$(./build.sh --version)" dist/what-renders-this.zip

build.sh refuses to build if the plugin header, WRT_INSPECTOR_VERSION and the Stable tag in readme.txt disagree, and asserts that the zip contains a single top-level what-renders-this/ directory. A flat zip unpacks loose into wp-content/plugins/ and WordPress will not recognise it as a plugin.

readme.txt is what wordpress.org reads; README.md is for GitHub. Both are shipped in the zip. Listing media — screenshots, banners, icons — lives in .wordpress-org/ and is never shipped; see the README in that directory for the required filenames.

License

GPL-2.0-or-later. See LICENSE.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.2.1 Aug 10, 2026 what-renders-this.zip 0
v1.1.0 Aug 7, 2026 wrt-inspector.zip 1
v1.0.0 Aug 7, 2026 wrt-inspector.zip 0