WP Debug Toolkit’s CLI includes a --profile flag that calls any REST endpoint and returns structured JSON with the full performance breakdown: execution time, memory usage, query count, duplicate detection, per-plugin query attribution, and the slowest queries with their SQL.
The output is machine-readable, so you can save it to a file, diff it against a previous run, pipe it through jq, or let an AI coding agent parse and interpret it. This is particularly useful for headless WordPress setups where there’s no admin panel to attach a browser-based profiler to, and for scripted workflows where you need repeatable, comparable measurements.
You can run these commands yourself or ask your AI coding assistant (Claude Code, Cursor, etc.) to do it. If wp isn’t available from your terminal — which is common when AI agents run outside a site shell — see the AI Assistant Guide or install the agent skill directly:
npx skills add WP-Debug-Toolkit/wpdt-cli
The basic command:
wp dbtk api call GET /wc/v3/products --params='{"per_page":3}' --profile
This calls WooCommerce’s products endpoint and returns the API response along with a profile section:
{
"status": 200,
"data": [ ... ],
"headers": {
"X-WP-Total": 20,
"X-WP-TotalPages": 7
},
"profile": {
"execution_time_ms": 19.63,
"memory": {
"delta_mb": 0.18,
"peak_mb": 117.79
},
"queries": {
"total": 22,
"slow": 0,
"duplicates": 12,
"by_type": { "SELECT": 22 },
"total_time_ms": 18.67,
"by_component": {
"wordpress-core": 19,
"woocommerce": 3
},
"slowest": []
},
"php_errors": []
}
}
The dispatch is internal — it goes through WordPress’s REST server without an actual HTTP request, so there’s no network overhead affecting the timing.
Each field in the profile tells you something specific:
**execution_time_ms** — Total time for the endpoint to process the request, in milliseconds. This includes PHP execution and all database queries.
**memory.delta_mb** — How much memory the endpoint used beyond what was already allocated. A high delta on a simple endpoint suggests unnecessary object loading.
**memory.peak_mb** — Peak memory usage during the request. Compare this against your PHP memory limit to gauge headroom.
**queries.total** — Total number of database queries the endpoint triggered. For context, a typical WordPress page load runs 20-40 queries. An endpoint running 100+ queries is worth investigating.
**queries.slow** — Number of queries that exceeded the slow threshold (50ms by default). Even one slow query can dominate total execution time.
**queries.duplicates** — Number of queries that were executed more than once with the same SQL (after normalizing values). Duplicates usually indicate an N+1 problem — for example, loading post meta one row at a time inside a loop instead of batch-fetching.
**queries.by_type** — Breakdown by SQL statement type. A healthy read endpoint should be mostly SELECTs. If you see INSERTs or UPDATEs on a GET endpoint, something unexpected is happening (logging, transient refreshes, or side effects).
**queries.by_component** — Which plugin, theme, or WordPress core triggered each query. This is the key field for answering “is my code the problem?” The component is identified from the file path in the call stack, so it works for any plugin — no configuration needed.
**queries.slowest** — The top five slowest queries with their SQL, execution time in milliseconds, and originating component.
**php_errors** — PHP errors, warnings, and notices that fired during the request. Available in full profile mode.
The most common question: “How many queries does my plugin add to this endpoint?”
Profile the endpoint and look at by_component:
wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile
If the output shows:
"by_component": {
"wordpress-core": 19,
"woocommerce": 3,
"my-discount-plugin": 8
}
That tells you your plugin is adding 8 queries to a request that would otherwise run 22. Whether that’s acceptable depends on context — but now you have a number to work with instead of a guess.
If your plugin doesn’t appear in by_component at all, it’s not triggering any database queries on that endpoint.
Save the profile output to a file before making changes:
wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile > /tmp/before.json
Make your code change. Then profile again:
wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile > /tmp/after.json
Compare the results. If you have jq installed:
echo "Before:" && cat /tmp/before.json | jq '.profile.queries | {total, slow, duplicates, total_time_ms}'
echo "After:" && cat /tmp/after.json | jq '.profile.queries | {total, slow, duplicates, total_time_ms}'
A meaningful improvement means total queries down, duplicates down, and total_time_ms down. If the numbers barely changed, the optimization target is elsewhere.
Duplicates are one of the most common performance issues in WordPress plugins. The profile’s duplicates field counts queries that executed more than once with the same normalized SQL.
“Normalized” means numeric values and string literals are replaced with placeholders before comparing. So SELECT * FROM wp_postmeta WHERE post_id = 42 and SELECT * FROM wp_postmeta WHERE post_id = 87 are treated as the same query.
If you see a high duplicate count, the likely cause is an N+1 pattern: loading related data inside a loop one item at a time, instead of batch-loading before the loop. Common examples:
update_meta_cache()update_object_term_cache()The slowest array may also help — if the same query pattern appears multiple times in the slowest list, that’s your duplicate.
Three modes are available depending on how much data you need:
# Full profile: timing, memory, all queries, PHP errors
wp dbtk api call GET /wp/v2/posts --profile
# Query analysis only: timing, memory, queries (no PHP error capture)
wp dbtk api call GET /wp/v2/posts --profile=queries
# Summary only: timing, memory, total query count
wp dbtk api call GET /wp/v2/posts --profile=summary
Use summary for quick checks. Use full when debugging a specific issue where you need the query breakdown and error capture.
The profiler works on any REST endpoint registered on the site — WordPress core, WooCommerce, Yoast, Elementor, or any custom plugin. If you need to find endpoints first:
# Discover all registered REST routes
wp dbtk api discover
# Search for endpoints related to orders
wp dbtk api search "order"
# See full details for a specific route (methods, parameters, auth)
wp dbtk api show /wc/v3/orders
Then call and profile whatever you find:
wp dbtk api call GET /wc/v3/orders --params='{"status":"processing"}' --profile
The profiler runs through WordPress’s internal REST server using the current WP-CLI user. If an endpoint requires authentication (most do), pass --user to the wp command:
wp dbtk api call GET /wc/v3/products --profile --user=1
If you get a 401 status in the response, the endpoint requires a user with the right capabilities. Use --user=<admin-user-id> to authenticate as an admin.