Bloatwright answers one question: which plugins make this site slow, and by how much? It measures each one on real pages of your own site.
Click Run profile. The plugin requests a handful of typical pages (home, the latest post, a page, the WooCommerce shop and a product when WooCommerce is active, the dashboard and the posts list) from your own server, several times each, with a profiler attached to those requests only. Visitors are never affected, nothing is deactivated, and no measurement data leaves your site (see External services below for the one optional, off-by-default kind of outbound request).
For every plugin, theme and must-use plugin you get:
plugins_loaded. That load time includes any hook callbacks that run while the file loads, so it overlaps with the time charged to those callbacks’ owners. Not measured separately: the theme’s functions.php load time and, on multisite, the load time of network-activated plugins (their hook callbacks are still timed).Everything rolls up into a relative weight score (PHP time 40 %, query time 20 %, render-blocking KB 20 %, autoload KB 10 %, cron runs/day 10 %; render-blocking KB counts only the scripts and styles that block rendering on front-end pages) and, where a rule applies, a plain-language verdict, such as “Adds about 180 ms of PHP on every front-end page, including pages that may not use it” or “Expensive only on checkout (240 ms); cheap elsewhere.”
Try without
Pick a plugin and re-run the same URLs with that plugin filtered out of the profiling requests only. You see the before/after of every page, compared with the session you started it from, without deactivating anything for visitors.
Browse-and-profile
If your host blocks loopback requests, start a recording instead (it lasts two minutes by default; change it under Settings): the pages you open in your own browser are measured, front-end and admin alike. The recording cookie is host-only, so on a site whose front end and admin use different hosts, only pages on the host of the Site Address (Settings General) are recorded. REST API, admin-ajax, cron, XML-RPC, login and media-upload requests and the Bloatwright screen itself are not measured, and each URL is recorded once. When the time is up the cookie expires and the recording ends; what it recorded is saved as a session the next time Bloatwright checks (the Bloatwright screen, or your next page view with the toolbar). A recording and an automatic run (including “Try without” and WP-CLI runs) never overlap: neither can start while the other is in progress.
History, compare and export
The newest twenty sessions are kept by default (Settings “Sessions to keep”); older ones are deleted when a new run or recording starts. On the results screen a session can be compared with the previous regular session to see what changed after an update or a plugin swap; to compare any two sessions use wp bloatwright profile compare <a> <b> or the REST API (/wp-json/bloatwright/v1/sessions/<b>/compare/<a>). The History tab lists past sessions and, once there are two finished regular sessions, a PHP-time trend for the heaviest plugins; every session can be exported as a self-contained HTML report you can send to a client or a developer.
Autoload and cron pages
Two extra tabs break down the autoloaded options and the cron queue by owner, including cron hooks that have no callback attached (usually left behind by a removed plugin).
WP-CLI
wp bloatwright profile run [--urls=<a,b>] [--passes=<n>] [--without=<plugin/file.php>] [--format=table|json] [--user=<id|login>]wp bloatwright profile listwp bloatwright profile compare <a> <b>wp bloatwright autoloadwp bloatwright cron
–user is WP-CLI’s global parameter: admin URLs are requested as that user and skipped (with a warning) when no user is given. Like the admin screen, profile run writes the must-use loader first when it is missing or out of date.
How it works
When a profiling run, a “Try without” run, a WP-CLI run or a browse-and-profile recording starts and the loader is missing or out of date, the plugin writes one small must-use plugin, 0-bloatwright-loader.php, into the must-use plugins folder (normally wp-content/mu-plugins). That file does nothing unless a request carries a signed, short-lived token; only then it starts the profiler, which sends no-cache headers, wraps hook callbacks with timers, records SAVEQUERIES output, reads the enqueued scripts and styles, and stores the result. You can remove the loader at any time from Settings Advanced (it is written again on the next run), and it is removed when Bloatwright is deactivated. On multisite it is kept while Bloatwright is still active on another site of the network, and removed on network deactivation or when the last site deactivates it.
Hooks for developers
bloatwright_urls_default, `bloatwright_owner_for_file`, `bloatwright_option_prefixes`, `bloatwright_verdicts`, `bloatwright_expected_slugs`, `bloatwright_session_complete`, `bloatwright_capability`, `bloatwright_loopback_sslverify`, `bloatwright_remote_head`, `bloatwright_remote_asset_owner`, `bloatwright_record_wait`, and the action `bloatwright_record`, which runs at shutdown at the end of every profiled request (warm-up passes included, though they are not stored) with the request's measurement record and its context (session id, URL, pass, excluded plugin).
Bloatwright profiles your site from your own server: every measurement request goes to your own site’s URLs. It has no server of its own and uses no third-party service or API. Nothing is sent anywhere by default; the one optional kind of outbound request is described below.
To show the file size of the remote scripts and styles your pages already load (for example a CDN copy of a library or a font stylesheet), Bloatwright can ask the host that serves each file for its size. There is no fixed provider: the hosts contacted are the ones your own pages already load files from, so their terms and privacy policies are those of the services you chose to use.
Mozilla/5.0 (compatible; Bloatwright/1.0.0) (the version follows the plugin version) and no cookies. Redirects are not followed, so a redirected asset’s size (like one sent without a Content-Length header) is shown as unknown.Everything Bloatwright stores stays in your own database:
akismet/akismet.php), the slowest database queries with their literal values (quoted strings, numbers, hex values) replaced by ? and, for each of them, the caller: the last three entries of WordPress’s own query caller summary, which are function and method names and, for include/require entries and closures (named after their file since PHP 8.4), file paths relative to the WordPress or wp-content folder; a file outside both is reduced to its file name. It also holds the URLs and sizes of enqueued scripts and styles.bloatwright_profile in the administrator’s own browser. It expires after the configured lifetime (two minutes by default) and is cleared when the recording stops.session_tokens user meta). It is ended when the step or WP-CLI run finishes, or when the PHP request running it ends; at worst it expires after ten minutes.No visitor requests are measured and no visitor data is recorded. Nothing is sent off-site by default; the only exception is the optional HEAD requests described under External services, which go only to asset URLs your pages already load and carry the User-Agent Mozilla/5.0 (compatible; Bloatwright/1.0.0).
Uninstalling always removes the must-use loader, the signing key and other run-state options, the cached remote asset sizes and every user’s dismissed-notice flag. It also drops the two tables (sessions and request rows) and deletes the settings, the attribution overrides and the schema version option, unless “Keep sessions when the plugin is uninstalled” is on in Settings; then those stay so a reinstall picks up the history. On multisite every site is cleaned up.