WP Debug Toolkit (WPDT) lets you combine wp dbtk api call --profile=queries for a fast headline check with the Query Logger for full per-query detail. Profiling returns query counts by component so you can confirm a problem exists and identify which plugin is responsible. The Query Logger then gives you the individual SQL text, per-query timing, and flags for slow queries and N+1 patterns. Together, they take you from “this endpoint runs 47 queries” to “38 of them come from woocommerce with six flagged as slow.”
A REST endpoint is responding slowly, returning an unexpectedly large payload, or generating a database load that seems disproportionate to what it should be doing. You’ve ruled out network latency and caching gaps. The problem is in the database layer, and you need to know which plugin, theme, or WordPress core component is responsible.
WPDT gives you two tools for this. The profiler built into wp dbtk api call --profile dispatches the endpoint internally and captures query counts, timing, memory usage, and a component-level breakdown without requiring any setup.
When that breakdown points to a specific plugin, the Query Logger lets you record every SQL query that runs during a live request and read them back with full SQL text, per-query execution time, and detection flags for slow queries and N+1 patterns. This guide walks you from a headline query count to a component-level, query-by-query trace.
| WPDT Tool | Role in This Guide |
|---|---|
REST API Tools (wp dbtk api call --profile) | Profiles the endpoint from the terminal and returns total query count, timing, memory, and a per-component query count breakdown. It is the starting point for confirming a database problem and identifying which component is responsible |
Query Logger (wp dbtk query-log) | Records every SQL query executed during the request with per-query component attribution, SQL text, execution time, and a slow-query flag. N+1 patterns are detectable via --format=json output. Use this when you need to see the individual queries, not just the counts |
wp dbtk query-log on before starting a recording. The db.php drop-in it installs enriches component attribution with additional detail such as affected row counts and query errors. Without it, the Query Logger still attributes each query to its source plugin, theme, or WordPress core using the backtrace string that SAVEQUERIES already capturesUse wp dbtk api call --profile=queries to confirm whether the endpoint has a database problem before recording a full session. The profiler dispatches the request internally and returns a component-level query count in the terminal. This process does not require any HTTP round-trip or separate setup.
bash
# Profile the endpoint — returns query count, timing, memory, and a per-component breakdown
# --profile=queries focuses on database behavior; omit PHP error capture (use --profile=full for that)
# Replace /wp/v2/posts with your endpoint path
wp dbtk api call GET /wp/v2/posts --profile=queries
# Add --params to pass query parameters
wp dbtk api call GET /wc/v3/products --profile=queries --params='{"per_page":10}'The profile.queries.by_component field in the JSON output lists query counts per source. A typical lightly-loaded endpoint shows a handful of queries attributed to wordpress-core. A problematic one shows a single plugin with 30 or 40 queries, where you would expect five or six.
✅ What You Should See: A JSON response with a profile block containing execution_time_ms, memory, and queries. The queries object includes total, slow, duplicates, by_type, by_component, and the five slowest queries. Check by_component first: it shows query count per plugin slug.
🔀 Decision Point: If by_component shows a high count from one plugin and timing confirms the slowdown, continue to Phase 2 for per-query SQL detail. If the query count and timing are within expected range, the database is not the source of the problem. You should run --profile=full and check the php_errors array instead.
The profiler shows counts and component totals. To see the individual SQL queries, start a Query Logger recording session, trigger the endpoint, and stop the recording.
bash
# Start a recording session with a tag you can use to filter later
# --duration is optional; omit it to record until you run stop (auto-stops after 1 hour)
wp dbtk query-log start --tag=api-endpoint-trace
# Trigger the endpoint — you can use wp dbtk api call, curl, or a browser request
# All three work because the recording session tag persists in the WordPress options table
# and the Query Logger reads it at shutdown, stamping the tag onto each request's query batch
# Option A: via wp dbtk api call (convenient — no separate HTTP request needed)
wp dbtk api call GET /wc/v3/products
# Option B: via curl (useful if you want to test with real HTTP headers or auth cookies)
# curl -s "https://yourdomain.com/wp-json/wc/v3/products" > /dev/null
# Stop the recording when done
wp dbtk query-log stop✅ What You Should See: After start, the terminal confirms Recording started with a tag name and expiry time. After stop, it confirms Recording stopped. The recording is now saved to disk and ready to read.
Read the recording you just captured and filter to the queries that matter.
bash
# Read all queries from your recording session
# --tag isolates this recording from any other requests logged at the same time
wp dbtk query-log read --tag=api-endpoint-trace
# Filter to queries from a specific component (use the plugin slug from Phase 1)
wp dbtk query-log read --tag=api-endpoint-trace --component=woocommerce
# Show only slow queries flagged by the Query Logger
wp dbtk query-log read --tag=api-endpoint-trace --slow
# Filter to a specific component and SQL type
wp dbtk query-log read --tag=api-endpoint-trace --component=woocommerce --type=SELECT
# Export to JSON: This is required to see N+1 flags (potential_n_plus_1: true on individual queries)
wp dbtk query-log read --tag=api-endpoint-trace --format=jsonThe table output shows four columns for each query: timing_ms, component, type, and the first 80 characters of sql. If you’d like the full SQL text and all detection flags, use --format=json.
✅ What You Should See: A table or JSON array of individual queries, each with its component attribution, SQL type, execution time, and the SQL text. After the table, a summary line reports total query count, slow query count, total execution time, and average time per query. Use --format=json to access per-query N+1 flags (potential_n_plus_1: true) that do not appear in the table view.
🔀 Decision Point: Use the following to classify what you found.
wp dbtk query-log read --tag=api-endpoint-trace --format=json to confirm — the affected queries will have potential_n_plus_1: true and a repeat count in the n_plus_1_count field.is_slow. Run EXPLAIN on the SQL text in your database client to investigate missing indexes or inefficient query structure.wp dbtk api call --profile alone tell me which plugin is causing the problem?Yes, it can at the component level. The by_component field in the profile output lists query counts per plugin slug, so you can see, for example, that WooCommerce generated 38 of the 47 total queries. What profiling does not give you is the individual SQL text for each of those 38 queries. If knowing the count and the component name is enough to act on the problem, Phase 1 alone may be sufficient. If you need to see the actual SQL (to identify which specific query is slow or to confirm an N+1 pattern), continue to the Query Logger recording in Phase 2.
Not strictly. Running wp dbtk query-log on before starting a recording is recommended because the db.php drop-in it installs enriches each query’s component attribution with additional detail, including affected row counts and query errors. Without the drop-in, the Query Logger still records queries and still identifies their source plugin, theme, or WordPress core file using the native backtrace string that SAVEQUERIES already attaches to every query. The profiler in Phase 1 does not require the drop-in at all: it captures queries through its own in-memory mechanism and returns results immediately in the CLI output.
Caching is the most likely cause. Page caching, object caching, and WordPress transients can all serve a cached response instead of running the full query stack, making the count appear to vary between calls. Before profiling, clear all caches and run the endpoint once to warm any persistent caches. Then profile the second call; that reflects the steady-state query load users will actually experience once the cache is warm.
Profiling Endpoint Performance – Covers all three profiling modes for wp dbtk api call in detail, including what each mode captures and when to use --profile=full versus --profile=queries versus --profile=summary.
Slow Query, N+1, and Duplicate Detection – Explains the thresholds that trigger a slow query flag, how the Query Logger identifies N+1 patterns across a request, and how duplicate queries are detected and counted.
Annotating Routes with Custom Metadata – Once you’ve identified a query-heavy endpoint, use wp dbtk api edit to attach a safety classification and notes so your team knows to treat it carefully before running it against production data.