Save Profiler
instance-save-profiler times the classic edit screen and saving a post.
It records how long every callback on the save hooks took and which of them ran the database queries, then stores a report you read over WP-CLI.
It is off until you switch it on, and it switches itself off again.
When To Reach For It
Section titled “When To Reach For It”Query Monitor shows what happens while a page renders. A save is a POST that redirects straight away, so there is nothing to look at: the work is done before the next screen loads. The profiler records that request and keeps the result.
It earns its place on sites with large ACF field groups, where a save can run thousands of queries without any single one being slow.
Using It
Section titled “Using It”# Profile for an hour, or pick the minutes yourselfddev wp instance profiler enableddev wp instance profiler enable --minutes=15
# Open or save a post in wp-admin, then read the newest reportddev wp instance profiler show
# Every stored report, and one by numberddev wp instance profiler listddev wp instance profiler show 3ddev wp instance profiler show --format=json
# Is it on, and how many reports are storedddev wp instance profiler status
# Stop early, and delete the reportsddev wp instance profiler disableddev wp instance profiler clearOn a server, drop the ddev prefix.
On multisite everything is per site, so add --url to pick one:
wp --url=https://example.com/other-site instance profiler enableReading a Report
Section titled “Reading a Report”An example report, trimmed, for a page with a large flexible content field:
Save post 496 by editor at 2026-09-15 09:29:39 UTCTotal 1857ms, peak memory 24MB, 241 POST vars (40 KB)
Slowest callbacks. Times are inclusive, so nested hooks overlap:+--------+-------+-------------------------------------------------------------+| ms | count | callback |+--------+-------+-------------------------------------------------------------+| 1053.4 | 2 | wp_after_insert_post @9 wp_save_post_revision_on_insert || 1046.2 | 1 | _wp_put_post_revision @10 acf_revisions::maybe_save_revision || 484.1 | 2 | save_post @10 ACF_Form_Post::save_post |+--------+-------+-------------------------------------------------------------+
6648 queries in 1091ms. By the callback that ran them:+-------+-------+---------------------------------------------------------+| ms | count | origin |+-------+-------+---------------------------------------------------------+| 765.0 | 5183 | wp_after_insert_post > wp_save_post_revision_on_insert || 302.0 | 1249 | save_post > ACF_Form_Post->save_post |+-------+-------+---------------------------------------------------------+- Slowest callbacks are timed individually. A callback that fires another hook includes that hook’s time, so the times do not add up to the total.
- By origin groups every query under the hook callback that ran it. This is usually where the answer is.
- Most repeated queries shows the shape of each query with its values replaced. A query run once per field is the thing to look for.
In the report above, saving a revision took over half the request: ACF copies every field value into each revision, and core reloads the revision’s meta before every copied field.
On a site like that, turning revisions off (add_filter('wp_revisions_to_keep', '__return_zero')) removes that work from every save.
What Gets Profiled
Section titled “What Gets Profiled”Only wp-admin/post.php: opening a post for editing and saving it.
Every other request returns before the plugin reads anything from the database, so it costs nothing to leave installed.
The block editor saves over the REST API and is not covered. Lumbermill disables the block editor, so posts save through post.php.
While a request is profiled, SAVEQUERIES is turned on for it. If a config file already sets SAVEQUERIES to false, the report has callback timings but no query breakdown.
Storage
Section titled “Storage”Reports live in the instance_save_profiler_reports option, newest first, and are not autoloaded.
Once the limit is reached the oldest report is dropped.
Nothing is written to a log file: web/app is served publicly, and a report names plugins, callbacks and file paths.
String and number literals are stripped from queries before they are stored, so a report never holds the content that was saved.
When instance-audit-log is active, enable and disable are recorded there.
Settings
Section titled “Settings”| Setting | Default | Meaning |
|---|---|---|
INSTANCE_SAVE_PROFILER_REPORTS | 10 | Reports kept per site |
INSTANCE_SAVE_PROFILER_MINUTES | 60 | How long enable lasts without --minutes |
Override them in config/application.php:
Config::define('INSTANCE_SAVE_PROFILER_REPORTS', 20);Filters
Section titled “Filters”| Filter | Use |
|---|---|
instance_save_profiler_hooks | Hooks whose callbacks are timed one by one |
save_post_{post_type} and edit_post_{post_type} are added for the post being profiled.

