# Wordpress Performance

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-iwritec0de-wp-dev-wordpress-performance`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [iwritec0de](https://agentstack.voostack.com/s/iwritec0de)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [iwritec0de](https://github.com/iwritec0de)
- **Source:** https://github.com/iwritec0de/wp-dev/tree/main/skills/wordpress-performance

## Install

```sh
agentstack add skill-iwritec0de-wp-dev-wordpress-performance
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# WordPress Performance

Performance optimization patterns for WordPress plugins, themes, and live sites.

## Critical Rules

1. **Never query without limits** — `posts_per_page => -1` and unbounded `$wpdb` queries are the most common cause of memory exhaustion on production sites.
2. **Cache expensive queries** — any query that runs on every page load and doesn't change per-request must be cached with transients or the object cache.
3. **Use `no_found_rows`** — set `'no_found_rows' => true` on any `WP_Query` that doesn't need pagination; this skips `SQL_CALC_FOUND_ROWS` which is expensive on large tables.
4. **Avoid meta queries on unindexed keys** — `meta_query` on `wp_postmeta` performs full table scans unless you add custom indexes. Consider a custom table for heavily queried data.
5. **Defer heavy work** — operations on `init` run on every request. Use `admin_init` for admin-only work, `rest_api_init` for REST-only, and `wp_loaded` or later hooks when possible.
6. **Prime caches, don't loop-query** — use `update_post_meta_cache()`, `update_post_caches()`, or `_prime_post_caches()` to batch-load metadata instead of calling `get_post_meta()` in a loop.

## Query Performance Patterns

### Efficient WP_Query

```php
// Good — limited, cache-friendly, no unnecessary data:
$query = new WP_Query( [
    'post_type'              => 'product',
    'posts_per_page'         => 20,
    'no_found_rows'          => true,   // Skip pagination count query.
    'update_post_meta_cache' => false,  // Skip if not reading meta.
    'update_post_term_cache' => false,  // Skip if not reading terms.
    'fields'                 => 'ids',  // Return only IDs when full objects aren't needed.
] );

// Bad — unbounded, forces full table scan:
$query = new WP_Query( [
    'post_type'      => 'product',
    'posts_per_page' => -1,  // Never do this.
] );
```

### N+1 Query Prevention

```php
// Bad — N+1 pattern (1 query per post in the loop):
foreach ( $posts as $post ) {
    $price = get_post_meta( $post->ID, '_price', true );  // Query per iteration.
}

// Good — prime the cache first, then loop reads from cache:
$post_ids = wp_list_pluck( $posts, 'ID' );
update_meta_cache( 'post', $post_ids );  // Single query loads all meta.

foreach ( $posts as $post ) {
    $price = get_post_meta( $post->ID, '_price', true );  // Reads from cache.
}
```

### Batch Operations

```php
// Bad — individual inserts in a loop:
foreach ( $items as $item ) {
    $wpdb->insert( $table, $item );  // N queries.
}

// Good — single bulk insert:
$values = [];
foreach ( $items as $item ) {
    $values[] = $wpdb->prepare( '(%d, %s, %f)', $item['user_id'], $item['status'], $item['total'] );
}
if ( $values ) {
    $wpdb->query( "INSERT INTO {$table} (user_id, status, total) VALUES " . implode( ', ', $values ) );
}
```

## Caching Strategies

### Transient Caching Pattern

```php
function myplugin_get_featured_products(): array {
    $cache_key = 'myplugin_featured_products';
    $data      = get_transient( $cache_key );

    if ( false !== $data ) {
        return $data;
    }

    $query = new WP_Query( [
        'post_type'      => 'product',
        'posts_per_page' => 12,
        'meta_key'       => '_featured',
        'meta_value'     => 'yes',
        'no_found_rows'  => true,
        'fields'         => 'ids',
    ] );

    $data = $query->posts;
    set_transient( $cache_key, $data, HOUR_IN_SECONDS );

    return $data;
}

// Invalidate when products change:
add_action( 'save_post_product', function (): void {
    delete_transient( 'myplugin_featured_products' );
} );
```

### Object Cache for Per-Request Deduplication

```php
function myplugin_get_settings(): array {
    $cached = wp_cache_get( 'settings', 'myplugin' );
    if ( false !== $cached ) {
        return $cached;
    }

    $settings = get_option( 'myplugin_settings', [] );
    wp_cache_set( 'settings', $settings, 'myplugin' );

    return $settings;
}
```

### When to Use Each Layer

| Scenario | Strategy |
|----------|----------|
| Same data fetched multiple times per request | `wp_cache_*` (object cache) |
| Expensive query, result valid for minutes/hours | `set_transient()` |
| External API response | `set_transient()` with timeout matching API rate limits |
| Data that changes on save | Transient + `delete_transient()` on `save_post` |
| User-specific data | Transient with user ID in key, or `get_user_meta()` |
| Full-page output | Consider `wp_cache_*` with a persistent backend (Redis/Memcached) |

## Hook Performance

### Defer Initialization

```php
// Bad — runs on every request including REST, AJAX, cron:
add_action( 'init', 'myplugin_heavy_init' );

// Good — scope to context:
add_action( 'admin_init', 'myplugin_admin_setup' );      // Admin only.
add_action( 'rest_api_init', 'myplugin_register_routes' ); // REST only.
add_action( 'template_redirect', 'myplugin_frontend' );    // Frontend only.

// Good — conditional loading:
add_action( 'init', function (): void {
    if ( ! is_admin() && ! wp_doing_ajax() && ! wp_doing_cron() ) {
        // Frontend-only logic.
    }
} );
```

### Lazy Loading

```php
// Bad — always loads all classes:
require_once __DIR__ . '/includes/class-admin.php';
require_once __DIR__ . '/includes/class-reports.php';
require_once __DIR__ . '/includes/class-import.php';

// Good — load only when needed:
add_action( 'admin_menu', function (): void {
    require_once __DIR__ . '/includes/class-admin.php';
    new Myplugin_Admin();
} );

// Best — PSR-4 autoloader via Composer:
require_once __DIR__ . '/vendor/autoload.php';
```

## Autoload Optimization

Options with `autoload = yes` are loaded into memory on every page load via a single `SELECT` from `wp_options`. Large or rarely-used options bloat this query.

```php
// Bad — large data autoloaded:
add_option( 'myplugin_log_history', $huge_array );  // Defaults to autoload = yes.

// Good — disable autoload for large or infrequent data:
add_option( 'myplugin_log_history', $huge_array, '', false );

// Or with update_option (autoload param is 4th arg since WP 4.2):
update_option( 'myplugin_log_history', $huge_array, false );
```

**Rule of thumb:** autoload only small, frequently-accessed options (settings, feature flags). Disable autoload for logs, large serialized arrays, and data accessed only on specific pages.

## Database Indexing

```php
// Add indexes to custom tables on activation:
register_activation_hook( __FILE__, function (): void {
    global $wpdb;
    $table = $wpdb->prefix . 'myplugin_orders';

    // Check before adding to make activation idempotent.
    $index_exists = $wpdb->get_var( $wpdb->prepare(
        'SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND INDEX_NAME = %s',
        DB_NAME, $table, 'status_created_idx'
    ) );

    if ( ! $index_exists ) {
        $wpdb->query( "ALTER TABLE {$table} ADD INDEX status_created_idx (status, created_at)" );
    }
} );
```

### Indexing Post Meta for Queries

If you frequently filter by a specific meta key, consider adding an index:

```sql
-- Only add this if meta_query on this key is a known bottleneck:
ALTER TABLE wp_postmeta ADD INDEX meta_key_value_idx (meta_key(50), meta_value(50));
```

**Warning:** modifying core tables is risky and may conflict with updates. Prefer custom tables for heavily queried structured data.

## Anti-Patterns

| Anti-Pattern | Impact | Fix |
|---|---|---|
| `posts_per_page => -1` | Loads unlimited posts into memory | Set a reasonable limit or paginate |
| `get_post_meta()` in a loop | N+1 queries | `update_meta_cache()` before loop |
| Heavy logic on `init` | Runs on every request | Use context-specific hooks |
| `get_option()` on every function call | Redundant queries (without persistent cache) | Cache in a static variable or `wp_cache_*` |
| Large serialized arrays in autoloaded options | Bloats the autoload query | Set autoload to `false` |
| `$wpdb->query()` inside `foreach` | N inserts instead of 1 | Batch insert with concatenated VALUES |
| `LIKE '%search%'` on large tables | Cannot use index (leading wildcard) | Full-text search index or prefix-only LIKE |
| Missing `no_found_rows` | Extra COUNT query when pagination isn't needed | Set `true` on non-paginated queries |
| `wp_remote_get()` on every page load | Blocks rendering on external service | Cache response with transient |
| Autoloading large options | Loaded on every request even when unused | `add_option()` with autoload `false` |

For query analysis and EXPLAIN usage, see `reference/query-optimization.md`.
For caching architecture and invalidation strategies, see `reference/caching-strategies.md`.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [iwritec0de](https://github.com/iwritec0de)
- **Source:** [iwritec0de/wp-dev](https://github.com/iwritec0de/wp-dev)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-iwritec0de-wp-dev-wordpress-performance
- Seller: https://agentstack.voostack.com/s/iwritec0de
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
