wp-plugin-dto

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.

Best use case

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

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.

Teams using wp-plugin-dto 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-dto/SKILL.md --create-dirs "https://raw.githubusercontent.com/nvdigitalsolutions/mcp-ai-wpoos/main/.agents/skills/wp-plugin-dto/SKILL.md"

Manual Installation

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

How wp-plugin-dto Compares

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

Frequently Asked Questions

What does this skill do?

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.

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 DTOs

For plugin code that moves structured data between request input, options,
meta, custom tables, external APIs, REST controllers, admin screens, cron jobs,
and presenters. A DTO gives that data one named shape instead of leaking raw
arrays across the plugin.

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

## When to load references

- Need a complete PHP 7.4 / PHP 8.1 DTO implementation, nested DTO, enum-like value set, or secret value object: read [references/native-dto-patterns.md](references/native-dto-patterns.md).
- Refactoring a controller/repository that currently passes raw arrays, `$_POST`, `WP_Post`, meta arrays, or option arrays around: read [references/before-after-raw-array.md](references/before-after-raw-array.md).

## Misconception this skill corrects

> "A DTO is just an array with nicer comments."

Wrong direction. A DTO is a small immutable boundary object with a named schema,
explicit defaults, and one hydration path. It prevents common AI mistakes:
unchecked `(int)` casts, `empty()` swallowing `0`, `isset()` hiding intentional
`null`, dynamic properties, leaking secrets through `to_array()`, and passing
`$_POST` or raw post meta directly to business logic.

## When to use this skill

Trigger when ANY of the following is true:

- Introducing `FooDto`, `FooData`, `SettingsData`, `RequestData`, `Payload`, or a value object.
- A repository, REST controller, admin page, cron job, or WooCommerce hook currently passes large associative arrays around.
- Data comes from `$_GET`, `$_POST`, REST params, options, post meta, user meta, term meta, custom tables, `WP_Post`, `WP_User`, or WooCommerce objects.
- Reviewing hydration / normalization code with casts like `(int)`, `(bool)`, `intval`, `settype`, `empty`, `isset`, `get_object_vars`, or dynamic property assignment.
- Presenter or REST response code needs a stable input object.

## Layer boundaries

| Layer | Responsibility |
|---|---|
| Source / repository | Read WP objects, options, meta, request params, API responses. Knows WordPress. |
| DTO | Hold normalized values. No DB writes, no HTML, no hooks, no global reads. |
| Validator | Decide whether the DTO is acceptable. Returns `true` or `WP_Error`. |
| Presenter | Convert DTO to arrays / JSON-ready values for REST, admin, JS config, email. |
| View / template | Escape and echo HTML. |

The DTO may contain small normalization helpers, but it should not call
`get_post_meta()`, `update_option()`, `wp_remote_get()`, `add_action()`, or echo
anything.

## Minimal class shape

Prefer PHP 7.4-compatible immutable objects unless the plugin has a higher
minimum PHP version. Use PHP 8.1 `readonly` only when the plugin declares PHP
8.1+.

```php
namespace MyPlugin\Dto;

use WP_Error;

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

final class ProductDto {
    private int $id;
    private string $title;

    public function __construct( int $id = 0, string $title = '' ) {
        $this->id    = $id;
        $this->title = $title;
    }

    /** @param array<string,mixed> $data @return self|WP_Error */
    public static function from_array( array $data ) {
        $errors = new WP_Error();
        $id     = self::to_int( $data['id'] ?? 0, 'id', $errors );
        $title  = self::to_string( $data['title'] ?? '', 'title', $errors );

        if ( '' === $title ) {
            $errors->add( 'missing_title', 'Product title is required.' );
        }
        if ( $errors->has_errors() ) {
            return $errors;
        }

        return new self( $id, $title );
    }

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

    public function id(): int { return $this->id; }
    public function title(): string { return $this->title; }

    private static function to_int( $value, string $field, WP_Error $errors ): int {
        if ( is_int( $value ) ) {
            return $value;
        }
        if ( is_string( $value ) && preg_match( '/^-?\d+$/', $value ) ) {
            return (int) $value;
        }
        $errors->add( 'invalid_' . $field, sprintf( '%s must be an integer.', $field ) );
        return 0;
    }

    private static function to_string( $value, string $field, WP_Error $errors ): string {
        if ( is_string( $value ) ) {
            return $value;
        }
        if ( is_scalar( $value ) ) {
            return (string) $value;
        }
        $errors->add( 'invalid_' . $field, sprintf( '%s must be a string.', $field ) );
        return '';
    }
}
```

Use [references/native-dto-patterns.md](references/native-dto-patterns.md) for
the full pattern: floats, booleans, DateTime, nested DTO collections, secrets,
`with()`, and PHP 8.1 variants.

## Hydration rules

- **One entry point.** Use `from_array()` or named factories like `from_post( WP_Post $post )`, `from_option( array $option )`, `from_request( WP_REST_Request $request )`.
- **Sanitize at the input boundary, validate in/near the DTO.** For `$_POST`, use `wp_unslash()` first, then a field-specific sanitizer.
- **Use allowlists.** Read only known keys. Do not keep unknown keys unless the DTO has a deliberate `extra` property.
- **Use `array_key_exists()` when `null` is meaningful.** `isset( $data['expires_at'] )` treats explicit `null` as absent.
- **Avoid `empty()` for typed fields.** `empty( '0' )` and `empty( 0 )` are true.
- **Coerce deliberately.** `(int) 'abc'` becomes `0`; `(bool) 'false'` becomes `true`. Reject surprising input with `WP_Error`.
- **Nested arrays become nested DTOs.** Do not leave `items` as random arrays when the plugin expects item shape.
- **No dynamic properties.** PHP 8.2 deprecates them. Declare every property.
- **Defaults are explicit.** Every constructor argument has a sensible default or is nullable.

## Sensitive fields

DTOs often carry API keys, tokens, customer emails, or internal notes.

- Keep secrets nullable: absence is `null`, not an empty secret string.
- Do not include secrets in `to_array()` unless the method name says so, e.g. `to_private_array()`.
- Prefer a tiny value object for credentials; see [references/native-dto-patterns.md](references/native-dto-patterns.md#secret-value-object).
- Let the presenter decide whether a field is redacted for REST/admin/export.

## Critical rules

- **DTO is immutable.** No setters. Use `with()` to produce a changed copy.
- **DTO is not a repository.** No `get_post_meta()`, no `update_option()`, no `$wpdb`, no HTTP calls.
- **DTO is not a presenter.** No HTML, no escaping, no `wp_send_json()`, no `rest_ensure_response()`.
- **Hydration is explicit and allowlisted.** Never `foreach ( $data as $key => $value ) { $dto->$key = $value; }`.
- **Validation returns `WP_Error` or throws only for programmer errors.** User input failures are normal and reportable.
- **Use field-specific coercion.** Do not use unchecked `(int)`, `(bool)`, `intval`, `settype`, or `empty()`-driven normalization.
- **Keep parameter/property names stable.** Renaming `created_at` to `createdAt` is a breaking change unless every caller and presenter is updated.
- **For PHP 8.1 enums, use `tryFrom()` and handle null.** For PHP 7.4-compatible plugins, use constants plus explicit `in_array( ..., true )`.

## Common mistakes

```php
// WRONG - unchecked casts hide bad input.
$dto = new ProductDto( (int) $_POST['id'], (float) $_POST['price'] );

// WRONG - dynamic property hydration.
foreach ( $row as $key => $value ) {
    $dto->{$key} = $value;
}

// WRONG - empty() rejects valid values.
if ( empty( $data['quantity'] ) ) {
    return new WP_Error( 'missing_quantity', 'Quantity is required.' );
}

// WRONG - leaking secrets by dumping all object properties.
return get_object_vars( $dto );
```

## Cross-references

- Run **`wp-plugin-presenter`** when converting this DTO into REST/admin/JS/email output.
- Run **`wp-plugin-architecture`** when deciding folder placement, namespaces, or by-feature vs by-type organization.
- Run **`wp-plugin-options-storage`** when the DTO represents an option payload.
- Run **`bd-data-object`** only if the project intentionally uses the better-data library. better-data automates many of these rules; this skill is the native no-library version.

## What this skill does NOT cover

- HTML templates and escaping strategy after presentation.
- Database schema migrations or custom table repositories.
- better-data attributes, sources, sinks, or presenter builder APIs.
- REST route registration; use the REST-specific plugin skill if present.

## References

- [references/native-dto-patterns.md](references/native-dto-patterns.md) - complete DTO implementation patterns.
- [references/before-after-raw-array.md](references/before-after-raw-array.md) - refactoring raw arrays into DTOs.
- WordPress input security: `wp_unslash()`, sanitization, validation, and escaping.
- `WP_Error` for user-input validation failures.

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-presenter

5
from nvdigitalsolutions/mcp-ai-wpoos

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.

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-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.