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

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.


What you need

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

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

Profile an endpoint

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.


Reading the profile output

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.


Check if your plugin adds overhead

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.


Compare before and after a code change

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.


Find duplicate queries

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:

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

The slowest array may also help — if the same query pattern appears multiple times in the slowest list, that’s your duplicate.


Profile modes

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.


Profile any endpoint, not just your own

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

Authentication

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.

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.