wp-plugin-presenter

Design and review native presenter classes in WordPress plugins without requiring better-data - converting DTOs or domain objects into REST arrays, admin table rows, JS config payloads, email variables, and public view models with allowlisted fields, context methods, redaction by default, locale/date/number formatting, no DTO mutation, and correct WordPress escaping boundaries. Use when adding FooPresenter, response mappers, admin-row arrays, wp_send_json payloads, rest_ensure_response data, wp_add_inline_script config, or when code returns raw DTOs, WP_Post, WC_Order, get_object_vars, json_encode, or unescaped HTML from controllers.

Best use case

wp-plugin-presenter is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Design and review native presenter classes in WordPress plugins without requiring better-data - converting DTOs or domain objects into REST arrays, admin table rows, JS config payloads, email variables, and public view models with allowlisted fields, context methods, redaction by default, locale/date/number formatting, no DTO mutation, and correct WordPress escaping boundaries. Use when adding FooPresenter, response mappers, admin-row arrays, wp_send_json payloads, rest_ensure_response data, wp_add_inline_script config, or when code returns raw DTOs, WP_Post, WC_Order, get_object_vars, json_encode, or unescaped HTML from controllers.

Teams using wp-plugin-presenter should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/wp-plugin-presenter/SKILL.md --create-dirs "https://raw.githubusercontent.com/nvdigitalsolutions/mcp-ai-wpoos/main/.agents/skills/wp-plugin-presenter/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/wp-plugin-presenter/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How wp-plugin-presenter Compares

Feature / Agentwp-plugin-presenterStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Design and review native presenter classes in WordPress plugins without requiring better-data - converting DTOs or domain objects into REST arrays, admin table rows, JS config payloads, email variables, and public view models with allowlisted fields, context methods, redaction by default, locale/date/number formatting, no DTO mutation, and correct WordPress escaping boundaries. Use when adding FooPresenter, response mappers, admin-row arrays, wp_send_json payloads, rest_ensure_response data, wp_add_inline_script config, or when code returns raw DTOs, WP_Post, WC_Order, get_object_vars, json_encode, or unescaped HTML from controllers.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# WordPress plugin: native presenters

For plugin code that turns DTOs / domain objects into output shapes. A
presenter chooses fields, computes labels, formats dates/numbers, redacts
sensitive values, and returns arrays that controllers can send to REST, AJAX,
admin tables, JS config, email templates, exports, or views.

This skill is intentionally **better-data-free**. If the project already uses
better-data, run `bd-presenter`; otherwise use this native pattern.

## When to load references

- Need complete REST/admin/JS/email/export presenter examples, collection presenter, or redaction pattern: read [references/presenter-context-patterns.md](references/presenter-context-patterns.md).
- Refactoring a controller that returns raw DTOs, raw arrays, `WP_Post`, `WC_Order`, `get_object_vars()`, or pre-escaped REST payloads: read [references/before-after-controller-output.md](references/before-after-controller-output.md).

## Misconception this skill corrects

> "The DTO already has `to_array()`, so the controller can return that everywhere."

Wrong. `to_array()` is usually the DTO's canonical data snapshot. REST output,
admin table rows, export rows, JS config, and email variables have different
audiences and redaction rules. A presenter makes those contexts explicit
instead of letting every controller hand-edit arrays.

## When to use this skill

Trigger when ANY of the following is true:

- Adding or reviewing `FooPresenter`, `FooViewModel`, `ResponseMapper`, `AdminRow`, `JsonPresenter`, or similar classes.
- REST/AJAX code returns arrays derived from DTOs, `WP_Post`, `WP_User`, WooCommerce objects, options, or custom table rows.
- Code calls `wp_send_json_success()`, `rest_ensure_response()`, `wp_add_inline_script()`, or builds admin table rows.
- A DTO has sensitive fields and the output needs redaction.
- A controller currently contains formatting, labels, computed fields, or output-specific conditionals.

## Layer boundaries

| Layer | Responsibility |
|---|---|
| DTO | Normalized data. No audience-specific output. |
| Presenter | Context-specific arrays and computed fields. No DB writes. |
| Controller | Permission check, nonce/REST validation, calls presenter, sends response. |
| View/template | Escapes and echoes HTML. |

Presenter output for REST/JSON should be raw JSON-safe primitives, not
pre-escaped HTML. Presenter output for an HTML-only view may include already
escaped markup, but the method name must make that clear, e.g.
`render_badge_html()`.

## Minimal class shape

```php
namespace MyPlugin\Presenter;

use MyPlugin\Dto\ProductDto;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

final class ProductPresenter {
    private ProductDto $product;

    public function __construct( ProductDto $product ) {
        $this->product = $product;
    }

    /** @return array<string,mixed> */
    public function for_rest(): array {
        return array(
            'id'      => $this->product->id(),
            'title'   => $this->product->title(),
            'enabled' => $this->product->enabled(),
        );
    }

    /** @return array<string,string|int> */
    public function for_admin_table(): array {
        return array(
            'id'     => $this->product->id(),
            'title'  => $this->product->title(),
            'status' => $this->product->enabled() ? __( 'Enabled', 'my-plugin' ) : __( 'Disabled', 'my-plugin' ),
        );
    }
}
```

Use explicit context methods instead of a generic `to_array( $context )` until
the contexts genuinely share most of the same shape. Method names make reviews
easier: `for_rest()`, `for_admin_table()`, `for_export()`, `for_email()`,
`for_js_config()`.

## Output rules by context

- **REST/AJAX:** return unescaped scalars, arrays, and nulls. Let WP JSON-encode them.
- **Admin table / template:** presenter chooses values; view escapes with `esc_html()`, `esc_attr()`, `esc_url()`, or `wp_kses_post()`.
- **Inline JS config:** pass presenter output through `wp_json_encode()` inside `wp_add_inline_script()`.
- **Email:** present subject/body variables separately from HTML template rendering.
- **Export:** use stable machine-readable keys and raw scalar values unless the export is explicitly human-facing.

See [references/presenter-context-patterns.md](references/presenter-context-patterns.md)
for complete examples.

## Redaction by default

Presenter methods should be public-safe by default. Sensitive values require
explicit opt-in:

```php
public function for_admin_table(): array {
    return array(
        'name'    => $this->credential->name(),
        'api_key' => '***',
    );
}

public function for_private_admin( bool $can_reveal_secret ): array {
    if ( ! $can_reveal_secret ) {
        return $this->for_admin_table();
    }

    $api_key = $this->credential->api_key();

    return array(
        'name'    => $this->credential->name(),
        'api_key' => $api_key ? $api_key->reveal() : '',
    );
}
```

The controller passes `$can_reveal_secret = current_user_can( 'manage_options' )`;
the presenter applies that already-made authorization decision. Do not create
convenience methods like `reveal_all()` or include secrets in a generic
`for_rest()` response.

## Critical rules

- **Presenter never mutates the DTO.** Compute output values into arrays.
- **Allowlist fields.** Never `get_object_vars( $dto )`, `json_encode( $dto )`, or return raw WP/WC objects.
- **One context, one method.** `for_rest()` and `for_admin_table()` should not share a leaky "everything" array.
- **Redact sensitive fields by default.** Explicit reveal only in narrowly named methods, and pass the authorization decision in from the controller.
- **Escape at the final HTML boundary.** REST/AJAX/JS config arrays are not HTML.
- **Do not put HTML in REST payloads.** If a method returns HTML, name it `render_*_html()` and escape inside it.
- **Keep DB and WP writes out.** Presenter may call formatting/i18n helpers, but not repositories, `update_option()`, `$wpdb`, or remote APIs.
- **Collections replay per-item presenters.** No duplicated mapping logic.

## Common mistakes

```php
// WRONG - exposes every public property and misses redaction.
return get_object_vars( $dto );

// WRONG - REST payload contains HTML from an admin use case.
return array( 'status' => '<span class="badge">Enabled</span>' );

// WRONG - escaping too early for JSON.
return array( 'title' => esc_html( $dto->title() ) );

// WRONG - side effect in presenter.
update_option( 'myplugin_last_presented', time() );
```

## Cross-references

- Run **`wp-plugin-dto`** when the presenter input is still a raw array or unclear object.
- Run **`wp-plugin-architecture`** when deciding folder placement, namespaces, or by-feature vs by-type organization.
- Run **`wp-plugin-assets-loading`** when passing presenter output into `wp_add_inline_script()`.
- Run **`bd-presenter`** only if the project intentionally uses the better-data library. better-data automates builder-style presentation; this skill is the native no-library version.

## What this skill does NOT cover

- DTO hydration and validation. Use `wp-plugin-dto`.
- Template partial organization or block rendering architecture.
- better-data Presenter internals.
- REST route registration and permission callbacks.

## References

- [references/presenter-context-patterns.md](references/presenter-context-patterns.md) - complete presenter context examples.
- [references/before-after-controller-output.md](references/before-after-controller-output.md) - controller refactor examples.
- `rest_ensure_response()` for REST controllers.
- `wp_send_json_success()` / `wp_send_json_error()` for AJAX.
- `wp_json_encode()` and `wp_add_inline_script()` for safe JS config.

Related Skills

wp-plugin-rewrite-rules

5
from nvdigitalsolutions/mcp-ai-wpoos

Design and review custom WordPress URL rewrites: add_rewrite_rule, add_rewrite_tag, query_vars, CPT/taxonomy rewrite slugs, add_rewrite_endpoint, soft vs hard flushes, rewrite_rules cache behavior, and the rule that flush_rewrite_rules() must not run on every request. Use for custom pretty URLs, CPT permalink 404s, endpoint rewrites, and code containing flush_rewrite_rules or add_rewrite_rule.

wp-plugin-options-storage

5
from nvdigitalsolutions/mcp-ai-wpoos

Picks the right WordPress storage primitive for plugin data: options, user/post/term/comment meta, transients, site options, site transients, or custom tables. Covers grouped settings, autoload management, transient TTL rules, serialized/JSON blob trade-offs, multisite storage caveats, and naming conventions. Use when scaffolding settings, choosing persistence for plugin-owned data, or auditing update_option/get_option/get_post_meta/set_transient/autoload usage.

wp-plugin-lifecycle

5
from nvdigitalsolutions/mcp-ai-wpoos

Designs and reviews the three lifecycle events of a WordPress plugin — activation (one-shot setup, dbDelta, add_option seeding, cron schedule, cap seeding), deactivation (reversible cleanup, cron clear via wp_unschedule_hook, never delete user data), and uninstall.php (standalone file, WP_UNINSTALL_PLUGIN guard, no autoloader, full data removal). Multisite-aware patterns using the $network_wide / $network_deactivating callback args, plus the recommendation against register_uninstall_hook in favor of uninstall.php. Use when scaffolding a plugin or debugging ghost cron events / orphan options. Triggers on register_activation_hook, register_deactivation_hook, uninstall.php, WP_UNINSTALL_PLUGIN, dbDelta, wp_unschedule_hook, switch_to_blog.

wp-plugin-hooks

5
from nvdigitalsolutions/mcp-ai-wpoos

Design custom action/filter hooks emitted by a plugin: action vs filter semantics, prefixed names, docblocks, parameter stability, *_ref_array forwarding, and deprecated hook migration. Use when adding, reviewing, evolving, or deprecating a public hook surface. Triggers on do_action, apply_filters, apply_filters_deprecated, do_action_deprecated, apply_filters_ref_array, do_action_ref_array, did_action, did_filter, or hook @since docblocks.

wp-plugin-dto

5
from nvdigitalsolutions/mcp-ai-wpoos

Design and review native DTOs in WordPress plugins without requiring better-data - immutable data carriers, explicit from_array hydration, strict coercion instead of unchecked casts, WP_Error validation failures, sensitive-field discipline, nested DTO arrays, and clear separation from repositories, WP models, presenters, REST controllers, and HTML views. Use when a plugin introduces FooDto, request DTOs, settings DTOs, value objects, admin-row data shapes, REST response source objects, or when reviewing code that passes raw arrays, stdClass, WP_Post, WC_Order, $_POST, post meta, or option arrays through multiple layers. Mentions better-data only as an optional higher-level library; this skill is for native implementations.

wp-plugin-cron

5
from nvdigitalsolutions/mcp-ai-wpoos

Designs and reviews scheduled/background work in WordPress plugins: wp_schedule_event, wp_schedule_single_event, cron_schedules, wp_next_scheduled guards, activation scheduling, deactivation cleanup, WP-Cron pseudo-cron timing, DISABLE_WP_CRON/system cron, multisite per-blog cron, idempotent callbacks, chunking, and Action Scheduler graduation. Use when adding scheduled jobs, debugging late/duplicate cron events, or deciding between WP cron and Action Scheduler.

wp-plugin-bootstrap

5
from nvdigitalsolutions/mcp-ai-wpoos

Scaffolds and reviews the main entry-point PHP file of a WordPress plugin — header (with Requires Plugins for WP 6.5+), ABSPATH guard, file/path/url/version constants, Composer + PSR-4 with manual spl_autoload_register fallback, register_activation_hook requirements check, Plugin class instantiation on plugins_loaded, and the WP 6.7+ rule that translation functions must not trigger before after_setup_theme. Use when scaffolding a new plugin or reviewing its main file. Triggers on Plugin Name headers, register_activation_hook, Requires Plugins, spl_autoload_register, plugins_loaded, or composer.json at the plugin root.

wp-plugin-assets-loading

5
from nvdigitalsolutions/mcp-ai-wpoos

Register and enqueue WordPress plugin scripts/styles with modern loading behavior, especially WP 6.9 fetchpriority support, script module args, footer placement, inline style limits, and removal of legacy IE conditional asset support. Covers wp_enqueue_script args strategy/in_footer/fetchpriority, wp_register_script_module / wp_enqueue_script_module args, wp_script_add_data, wp_style_add_data, dependency handles, conditional enqueueing on the right hook, and avoiding global frontend/admin asset bloat. Use when adding or reviewing plugin JS/CSS enqueue code.

wp-plugin-architecture

5
from nvdigitalsolutions/mcp-ai-wpoos

Designs and reviews the internal architecture of a WordPress plugin — folder layout, PSR-4 one-class-per-file discipline, Schema/Constants placement, composition-root vs singleton decisions, conditional asset enqueueing, script config via wp_add_inline_script, and prefixed custom-hook naming. Use when scaffolding includes/src, reviewing class organization, asset enqueue code, repeated strings, or "should I make this a singleton" decisions.

webapp-testing

5
from nvdigitalsolutions/mcp-ai-wpoos

Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

web-artifacts-builder

5
from nvdigitalsolutions/mcp-ai-wpoos

Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.

valyu

5
from nvdigitalsolutions/mcp-ai-wpoos

Search the live web and 36+ specialised data sources including SEC filings, PubMed, ChEMBL, clinical trials, FRED economic indicators, and patent databases. Use when current, authoritative, or paywalled data is required.