WP Debug Toolkit 1.2.0 is LIVE. Get $300 discount on the lifetime deal now
Use Discount Code WPDTLTD
Get WP Debug Toolkit

wp dbtk api call <method> <route> --profile calls any registered REST endpoint through WordPress’s internal REST server and returns structured JSON with a full performance breakdown: execution time, memory usage, database query count, per-component query attribution, duplicate detection, and the five slowest queries with their SQL. This makes terminal-based profiling the right approach for headless WordPress setups, CI pipelines, and AI-assisted development sessions where browser-based profilers are not an option.

In This Guide

Prerequisites

  • WP Debug Toolkit installed and activated on the target site
  • WP-CLI accessible from your terminal
  • A running WordPress site with data to query against

To use these commands from an AI coding assistant, install the WPDT agent skill:

bash

npx skills add WP-Debug-Toolkit/wpdt-cli

See How to Use WP Debug Toolkit with AI Coding Assistants and CLI Environment Setup for full setup details.

Profile an Endpoint

Run the command with --profile appended:

bash

wp dbtk api call GET /wc/v3/products --params='{"per_page":3}' --profile

WPDT dispatches the request through WordPress’s REST server internally using rest_do_request(). There is no HTTP connection, DNS lookup, or network overhead. The timing numbers reflect only PHP execution and database query time.

The response contains four top-level keys. The profile block is appended as a sibling to status, data, and headers:

json

{

  "status": 200,                      // HTTP status code

  "data": [ ... ],                    // REST response body

  "headers": {                        // REST response headers

    "X-WP-Total": 20,

    "X-WP-TotalPages": 7

  },

  "profile": {                        // Profiling data; not part of the REST response

    "execution_time_ms": 19.63,       // Total PHP execution time in milliseconds

    "memory": {

      "delta_mb": 0.18,               // Memory consumed by this request

      "peak_mb": 117.79               // Peak memory usage during the request

    },

    "queries": {

      "timings_available": true,      // false if SAVEQUERIES could not be enabled

      "total": 22,                    // Total queries triggered by the endpoint

      "slow": 0,                      // Queries exceeding the 50ms threshold

      "duplicates": 12,               // Redundant executions after normalization (N-1 per repeated pattern)

      "by_type": {

        "SELECT": 22                  // Counts by SQL statement type

      },

      "total_time_ms": 18.67,         // Total time spent in the database

      "by_component": {

        "wordpress-core": 19,         // Queries from WordPress core

        "woocommerce": 3              // Queries from WooCommerce

      },

      "slowest": []                   // Top 5 slowest queries; empty when slow === 0

    },

    "php_errors": []                  // PHP errors and warnings (full mode only)

  }

}

When the endpoint returns an error, the response shape changes. There is no data or headers key. WPDT returns status and an error object instead:

json

{

  "status": 401,

  "error": {

    "code": "rest_forbidden",

    "message": "Sorry, you are not allowed to do that."

  }

}

If you passed --profile, the profile block is still included alongside status and error.

✅ What You Should See: A JSON object with "status": 200 for a successful call and a profile block containing timing, memory, and query data. A "status": 401 or "status": 403 response means the request needs a different user. Add --user=<id> to authenticate as a specific WordPress user.

Reading the Profile Output

FieldWhat It Tells You
profile.execution_time_msTotal time from dispatch to response in milliseconds, including PHP execution and all database queries. Use this as your primary before/after benchmark when measuring the impact of a code change.
profile.memory.delta_mbMemory consumed by the endpoint beyond what was already allocated when the request started. A high delta on a simple endpoint suggests unnecessary object loading or unneeded data retrieval.
profile.memory.peak_mbPeak memory usage during the request. Compare this against your server’s memory_limit to gauge available headroom for concurrent requests.
profile.queries.timings_availabletrue when WPDT can capture per-query timing data (either SAVEQUERIES was already active or WPDT enabled it for this request). When false, the slow and total_time_ms fields are null, slowest is an empty array, and a capture_warning message explains why.
profile.queries.totalTotal database queries the endpoint triggered. A typical WordPress page load runs 20 to 40 queries; a well-optimized REST endpoint returning a list should stay in that range or below. 100-plus queries on a simple endpoint is a strong signal of an N+1 pattern.
profile.queries.slowQueries that exceeded the 50ms slow threshold. Even a single slow query can account for the majority of execution_time_ms.
profile.queries.duplicatesThe number of redundant query executions. For each distinct query pattern that ran N times after normalization, this adds N−1. A high count usually indicates an N+1 pattern, where related data is fetched one item at a time inside a loop instead of batch-loaded before the loop starts.
profile.queries.by_typeQuery count broken down by SQL statement type (SELECT, INSERT, UPDATE, etc.). A read endpoint generating INSERT or UPDATE statements signals unintended side effects such as transient refreshes or audit logging.
profile.queries.total_time_msTotal time the endpoint spent executing database queries. Compare this against execution_time_ms to determine whether the bottleneck is in the database or in PHP logic between queries.
profile.queries.by_componentQuery count per plugin, theme, or WordPress core. WPDT identifies each component by inspecting the call stack and matching the originating file path against plugin, theme, and core path patterns. No configuration is required.
profile.queries.slowestThe five slowest queries, each with its sql, time_ms, and component. Only populated when at least one query exceeds the 50ms threshold.
profile.php_errorsPHP errors, warnings, notices, and deprecations that fired during the request. Only captured when using --profile or --profile=full.

Check Whether Your Plugin Adds Overhead

Profile the endpoint and examine by_component:

bash

wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile

For example, if the output includes:

json

"by_component": {

  "wordpress-core": 19,

  "woocommerce": 3,

  "my-discount-plugin": 8

}

It means that your plugin adds 8 queries to a request that would otherwise run 22 queries. Whether that is acceptable depends on the endpoint’s complexity, but now you have a number rather than a guess.

If your plugin does not appear in by_component at all, it is not triggering any database queries on that endpoint.

Compare Before and After a Code Change

Save the profile before making changes:

bash

wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile > /tmp/before.json

Make your change, then profile again:

bash

wp dbtk api call GET /wc/v3/products --params='{"per_page":5}' --profile > /tmp/after.json

If you have jq installed, compare the key metrics directly:

bash

# Before

cat /tmp/before.json | jq '.profile.queries | {total, slow, duplicates, total_time_ms}'

# After

cat /tmp/after.json | jq '.profile.queries | {total, slow, duplicates, total_time_ms}'

A meaningful improvement: total down, duplicates down, total_time_ms down. If the numbers are unchanged, the bottleneck is elsewhere in the request lifecycle.

Detect Duplicate Queries

The queries.duplicates field counts redundant query executions, not distinct repeated patterns. If the same normalized query ran 13 times, it contributes 12 to this count. WPDT normalizes SQL by replacing single-quoted and double-quoted string literals, standalone numeric values, and IN (...) clause contents before comparing. 

This means SELECT * FROM wp_postmeta WHERE post_id = 42 and SELECT * FROM wp_postmeta WHERE post_id = 87 register as the same query pattern.

A high duplicate count almost always indicates an N+1 pattern. Common examples in WordPress plugins:

  • Loading post meta per post instead of using update_meta_cache()
  • Fetching term relationships per post instead of using update_object_term_cache()
  • Querying user data per comment instead of priming the user cache

Check queries.slowest as well. If the same query pattern appears multiple times among the five slowest entries, that pattern is your primary target.

For the full mechanism behind slow query detection and duplicate identification, see Slow Query, N+1, and Duplicate Detection.

Profile Modes

Three modes control how much data --profile collects:

bash

# Full mode (default): execution time, memory, complete query analysis, PHP error capture

wp dbtk api call GET /wp/v2/posts --profile

# Queries mode: execution time, memory, complete query analysis (no PHP error capture)

wp dbtk api call GET /wp/v2/posts --profile=queries

# Summary mode: execution time, memory, and total query count only

wp dbtk api call GET /wp/v2/posts --profile=summary

--profile without a mode value behaves identically to --profile=full. Use summary for quick sanity checks between iterations. Use --profile=queries when you need the complete query breakdown without the overhead of PHP error capture. Use full when you need both.

For a conceptual comparison of what each mode captures and how WPDT hooks into query logging, see Profiling Endpoint Performance.

Discover Endpoints First

If you are not sure which endpoints to profile, use the discovery commands:

bash

# Scan and store all registered REST routes on this site

wp dbtk api discover

# Search routes by keyword

wp dbtk api search "order"

# Inspect a specific route: methods, parameters, and auth requirements

wp dbtk api show /wc/v3/orders

Then profile whatever you find:

bash

wp dbtk api call GET /wc/v3/orders --params='{"status":"processing"}' --profile

The profiler works on any REST endpoint registered on the site, including WordPress core, WooCommerce, Yoast, Elementor, and any custom plugin.

Authentication

WPDT uses the current WP-CLI user identity for the dispatched request. Pass --user=<id> to authenticate as a specific WordPress user:

bash

wp dbtk api call GET /wc/v3/products --profile --user=1

A "status": 401 means no authenticated user was attached to the request. A "status": 403 means a user was authenticated but lacks the required capabilities. Either way, pass --user=<admin-user-id> to authenticate as a user with full access.

Frequently Asked Questions

Does wp dbtk api call --profile make a real HTTP request to my site?

No. WPDT dispatches the request through WordPress’s REST server using rest_do_request(), with no HTTP connection, DNS resolution, or network overhead. The execution_time_ms value in the profile reflects only PHP execution time and database query time for the endpoint itself, giving you timing data that isolates application performance from network conditions.

What query count is considered high for a REST endpoint?

Context determines the answer, but a few benchmarks help. A typical WordPress page load runs 20 to 40 queries. A simple REST endpoint returning a single object should stay well under 20. An endpoint returning a list of 10 items that generates 80 to 100 queries is almost certainly hitting an N+1 pattern. Use by_component to find where the queries originate and duplicates to confirm whether the same pattern is repeating.

Related Documentation

Profiling Endpoint Performance – Learn how each profile mode scopes its data capture and why query profiling works on any site regardless of SAVEQUERIES state.

Slow Query, N+1, and Duplicate Detection – Dig into the detection logic behind the slow, duplicates, and slowest fields so you know exactly what you’re looking at when the numbers are high.

How to Use WP Debug Toolkit with AI Coding Assistants – Get your AI coding assistant running wp dbtk api call --profile directly from its terminal without manual configuration.

On this page
Try WP Debug Toolkit
The best error log viewer with amazing developer tools to help you troubleshoot your WordPress site securely and efficiently. Something something more.