Showcase Project Events
by Victoria Bondar · github.com/violika7-cell/wordpress-plugin-architecture-example
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/violika7-cell/wordpress-plugin-architecture-example/archive/refs/heads/main.zipReadme
WordPress Plugin Architecture Example
A compact WordPress plugin showcasing clean boundaries, a custom database table, an application service, idempotent writes, REST endpoints, capability checks, and unit-testable domain logic.
Showcase note: all code and data in this repository are synthetic. No client implementation or proprietary logic is included.
Problem
A WordPress application needs to record project status events from multiple sources. A naive plugin might write directly to $wpdb from REST callbacks and duplicate events when a request is retried.
This example demonstrates a safer design:
- controllers handle HTTP concerns;
- services enforce business rules;
- repositories own persistence;
- domain objects validate state;
- idempotency keys prevent duplicate writes;
- schema creation is isolated in an installer.
Architecture
REST Controller
|
v
ProjectEventService
|
v
ProjectEventRepositoryInterface
|
v
WpdbProjectEventRepository
|
v
custom wp_showcase_project_events table
What this repository demonstrates
- WordPress plugin bootstrap without theme coupling
- dependency injection
- custom table migration with
dbDelta() $wpdb->prepare()usage- idempotent application service
- explicit domain validation
- REST schema validation
- capability-based authorization
- safe error responses
- unit tests without a WordPress runtime
- CI with multiple PHP versions
Installation
Requirements:
- PHP 8.1+
- WordPress 6.x+
- Composer for tests/autoloading
composer install
Place the plugin directory in wp-content/plugins/ and activate Showcase Project Events.
Activation creates a small custom table using the WordPress database prefix.
REST API
Create an event
POST /wp-json/showcase-events/v1/projects/123/events
Content-Type: application/json
X-WP-Nonce: <nonce>
{
"type": "deployed",
"message": "Release 1.4 deployed to staging",
"idempotency_key": "deploy-123-release-1.4"
}
Allowed event types in this demo:
createdupdateddeployedfailed
Repeated requests with the same idempotency key return the existing event instead of inserting a duplicate.
List project events
GET /wp-json/showcase-events/v1/projects/123/events
Decisions
Custom table instead of posts
Event data is append-oriented and operational. A dedicated table avoids forcing log-like records into the posts/meta model and makes indexing explicit.
Idempotency at the application boundary
Retries are common in webhooks, jobs, and unreliable networks. The service requires a caller-supplied idempotency key and the table enforces uniqueness.
Repository interface
Business logic does not know about $wpdb. That keeps the service independently testable and makes persistence replaceable.
Domain validation
ProjectEvent validates project IDs, event types, message length, and idempotency keys before persistence.
Error handling
- Validation failures return HTTP 400.
- Unauthorized requests are blocked by
permission_callback. - Duplicate idempotency keys resolve to the existing record.
- Database write failures become controlled application exceptions.
- Internal SQL details are never returned to the browser.
Security considerations
- REST endpoints require
manage_optionsin this showcase. - All route parameters and JSON fields are validated.
- SQL values use
$wpdb->prepare()or safe insert helpers. - Output uses structured REST responses rather than raw HTML.
- No secrets or credentials are stored by this plugin.
- Table names use the WordPress prefix and are never accepted from user input.
- Error logs should not include sensitive payloads in a real application.
Testing
composer test
Unit tests cover:
- event validation;
- new event recording;
- retry/idempotency behavior;
- repository failure propagation.
Production extensions
For a real product I would consider:
- dedicated capabilities rather than
manage_options; - pagination/cursor support;
- retention/archival policy;
- structured logs and correlation IDs;
- audit actor/source fields;
- migration versioning;
- integration tests against WordPress;
- observability metrics;
- background queue processing where event volume is high.
License
MIT