Agent skill
wordpress
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/wordpress-ar4mirez-samuel
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- framework
- language
- php
- extensions
- .php
SKILL.md
WordPress Framework Guide
Applies to: WordPress 6.0+, PHP 8.0+, Plugin Development, Theme Development, REST API, Block Editor (Gutenberg) Language Guide: @.claude/skills/php-guide/SKILL.md
Overview
WordPress is a content management system (CMS) powering over 40% of the web. This guide covers modern WordPress development including plugin architecture, theme development, REST API endpoints, the Block Editor (Gutenberg), and security essentials.
Use WordPress when:
- Content management system is needed
- Blog or publishing platform
- E-commerce with WooCommerce
- Custom applications with familiar admin UI
- Rapid prototyping with existing ecosystem
Consider alternatives when:
- Building pure API backend (use Laravel/Symfony)
- High-performance requirements (consider headless)
- Complex business logic applications
- Microservices architecture
Guardrails
WordPress-Specific Rules
- Use
declare(strict_types=1)in all PHP files - Prevent direct file access:
if (!defined('ABSPATH')) { exit; } - Use namespaces for all plugin/theme classes
- Escape all output:
esc_html(),esc_attr(),esc_url(),wp_kses_post() - Sanitize all input:
sanitize_text_field(),sanitize_email(),absint() - Verify nonces on all form submissions and AJAX requests
- Check capabilities before performing actions:
current_user_can() - Use
$wpdb->prepare()for all database queries (never concatenate) - Register all scripts/styles through
wp_enqueue_scriptshook - Use text domains and
__()/_e()for all user-facing strings - Set
show_in_rest => truefor post types and taxonomies that need Gutenberg/REST support - Use
register_post_meta()to expose meta fields in the REST API - Always include
uninstall.phporregister_uninstall_hook()for cleanup
Anti-Patterns
- Do not use
query_posts()(useWP_Queryorget_posts()) - Do not modify core files (use hooks and filters)
- Do not hardcode URLs (use
home_url(),admin_url(),plugin_dir_url()) - Do not store business logic in template files
- Do not skip nonce verification on any form or AJAX handler
- Do not use
extract()on untrusted data - Do not echo unsanitized user input
- Do not use
$_GET/$_POSTdirectly without sanitization
Project Structure
Plugin Structure
my-plugin/
├── my-plugin.php # Main plugin file (header, constants, bootstrap)
├── includes/
│ ├── class-plugin.php # Main plugin class (singleton)
│ ├── class-activator.php # Activation hooks
│ ├── class-deactivator.php # Deactivation hooks
│ ├── admin/
│ │ ├── class-admin.php # Admin functionality
│ │ └── partials/ # Admin templates
│ ├── public/
│ │ ├── class-public.php # Public functionality
│ │ └── partials/ # Public templates
│ ├── api/
│ │ └── class-rest-api.php # REST API endpoints
│ └── blocks/
│ └── my-block/ # Gutenberg blocks
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
├── languages/ # Translation files (.pot, .po, .mo)
├── templates/ # Overridable template files
├── tests/phpunit/
├── composer.json
├── package.json
└── readme.txt # WordPress.org readme
Theme Structure
my-theme/
├── style.css # Theme metadata (required)
├── functions.php # Theme setup and hooks
├── index.php # Fallback template (required)
├── header.php / footer.php # Header/footer templates
├── single.php / page.php # Single post / page templates
├── archive.php / 404.php # Archive / error templates
├── search.php / sidebar.php # Search / sidebar templates
├── inc/ # Customizer, template functions, hooks
├── template-parts/ # Reusable content partials
├── assets/ # CSS, JS, images
├── parts/ # Template parts (FSE)
├── patterns/ # Block patterns
├── templates/ # Block templates (FSE)
└── theme.json # Theme configuration (FSE)
Template Hierarchy
WordPress resolves templates from most specific to least specific. Pattern: {type}-{slug}.php -> {type}-{id}.php -> {type}.php -> index.php
- Single:
single-{post-type}-{slug}->single-{post-type}->single->singular->index - Page:
page-{slug}->page-{id}->page->singular->index - Archive:
archive-{post-type}->archive->index - Category:
category-{slug}->category-{id}->category->archive->index - Taxonomy:
taxonomy-{tax}-{term}->taxonomy-{tax}->taxonomy->archive - Search/404:
search.php/404.php->index.php
Plugin Basics
Main Plugin File
<?php
/**
* Plugin Name: My Plugin
* Plugin URI: https://example.com/my-plugin
* Description: A modern WordPress plugin
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 8.0
* Author: Your Name
* Text Domain: my-plugin
* Domain Path: /languages
*/
declare(strict_types=1);
namespace MyPlugin;
if (!defined('ABSPATH')) {
exit;
}
define('MY_PLUGIN_VERSION', '1.0.0');
define('MY_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('MY_PLUGIN_URL', plugin_dir_url(__FILE__));
define('MY_PLUGIN_BASENAME', plugin_basename(__FILE__));
require_once MY_PLUGIN_PATH . 'vendor/autoload.php';
register_activation_hook(__FILE__, [Activator::class, 'activate']);
register_deactivation_hook(__FILE__, [Deactivator::class, 'deactivate']);
add_action('plugins_loaded', function (): void {
Plugin::getInstance()->init();
});
Singleton Plugin Class
<?php
declare(strict_types=1);
namespace MyPlugin;
final class Plugin
{
private static ?self $instance = null;
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {}
public function init(): void
{
load_plugin_textdomain('my-plugin', false, dirname(MY_PLUGIN_BASENAME) . '/languages');
if (is_admin()) {
new Admin\Admin();
}
new Frontend\Frontend();
new Api\RestApi();
new Blocks\BlockManager();
}
}
Hooks and Filters
Common Hook Patterns
// Actions (do something at a specific point)
add_action('init', [$this, 'registerPostTypes']);
add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']);
add_action('save_post', [$this, 'onSavePost'], 10, 3);
add_action('wp_ajax_my_action', [$this, 'handleAjax']);
add_action('wp_ajax_nopriv_my_action', [$this, 'handleAjax']);
add_action('rest_api_init', [$this, 'registerRoutes']);
// Filters (modify data and return it)
add_filter('the_content', [$this, 'filterContent']);
add_filter('the_title', [$this, 'filterTitle'], 10, 2);
add_filter('excerpt_length', fn() => 30);
add_filter('post_class', [$this, 'addPostClasses'], 10, 3);
// Custom hooks (for extensibility)
do_action('my_plugin_after_save', $postId, $data);
$value = apply_filters('my_plugin_format_price', $price, $currency);
Asset Enqueuing
public function enqueueAssets(): void
{
wp_enqueue_style('my-plugin-style', MY_PLUGIN_URL . 'assets/css/public.css', [], MY_PLUGIN_VERSION);
wp_enqueue_script('my-plugin-script', MY_PLUGIN_URL . 'assets/js/public.js', ['jquery'], MY_PLUGIN_VERSION, true);
wp_localize_script('my-plugin-script', 'MyPluginData', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('my_plugin_nonce'),
'strings' => [
'loading' => __('Loading...', 'my-plugin'),
'error' => __('An error occurred.', 'my-plugin'),
],
]);
}
REST API
Custom Endpoint Pattern
<?php
declare(strict_types=1);
namespace MyPlugin\Api;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;
final class BooksController extends WP_REST_Controller
{
protected $namespace = 'my-plugin/v1';
protected $rest_base = 'books';
public function registerRoutes(): void
{
register_rest_route($this->namespace, '/' . $this->rest_base, [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'getItems'],
'permission_callback' => [$this, 'getItemsPermissions'],
'args' => $this->getCollectionParams(),
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'createItem'],
'permission_callback' => [$this, 'createItemPermissions'],
],
]);
}
public function getItems(WP_REST_Request $request): WP_REST_Response
{
$query = new \WP_Query([
'post_type' => 'book',
'posts_per_page' => $request->get_param('per_page') ?? 10,
'paged' => $request->get_param('page') ?? 1,
]);
$items = array_map(fn($post) => $this->formatItem($post), $query->posts);
$response = new WP_REST_Response($items, 200);
$response->header('X-WP-Total', $query->found_posts);
$response->header('X-WP-TotalPages', $query->max_num_pages);
return $response;
}
public function getItemsPermissions(): bool { return true; }
public function createItemPermissions(): bool { return current_user_can('publish_posts'); }
}
REST API conventions:
- Extend
WP_REST_Controllerfor full CRUD endpoints - Always define
permission_callback(use__return_truefor truly public) - Sanitize input parameters with
sanitize_callbackinargs - Return
WP_Errorfor error responses with proper status codes - Use pagination headers:
X-WP-Total,X-WP-TotalPages - Version your namespace:
my-plugin/v1
Block Editor (Gutenberg)
Block Registration (PHP)
// Register from block.json (preferred)
register_block_type(MY_PLUGIN_PATH . 'blocks/my-block');
// Dynamic block with server render
register_block_type('my-plugin/featured-items', [
'render_callback' => [$this, 'renderFeaturedItems'],
'attributes' => [
'count' => ['type' => 'number', 'default' => 3],
],
]);
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/my-block",
"version": "1.0.0",
"title": "My Block",
"category": "widgets",
"icon": "admin-generic",
"supports": {
"html": false,
"align": ["wide", "full"],
"color": { "background": true, "text": true },
"spacing": { "margin": true, "padding": true }
},
"attributes": {
"content": { "type": "string", "default": "" }
},
"textdomain": "my-plugin",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php"
}
Block JavaScript (index.js)
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import ServerSideRender from '@wordpress/server-side-render';
registerBlockType('my-plugin/my-block', {
edit: ({ attributes, setAttributes }) => {
const blockProps = useBlockProps();
return (
<>
<InspectorControls>
<PanelBody title={__('Settings', 'my-plugin')}>
<ToggleControl
label={__('Show Image', 'my-plugin')}
checked={attributes.showImage}
onChange={(val) => setAttributes({ showImage: val })}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<ServerSideRender block="my-plugin/my-block" attributes={attributes} />
</div>
</>
);
},
save: () => null, // Dynamic block: rendered server-side
});
Security Essentials
Input Sanitization
$title = sanitize_text_field($_POST['title']);
$email = sanitize_email($_POST['email']);
$url = esc_url_raw($_POST['url']);
$content = wp_kses_post($_POST['content']);
$filename = sanitize_file_name($_POST['filename']);
$key = sanitize_key($_POST['key']);
$int = absint($_POST['number']);
Output Escaping
echo esc_html($title); // HTML context
echo esc_attr($attribute); // Attribute context
echo esc_url($url); // URL context
echo esc_js($script); // JS context
echo wp_kses_post($content); // Allow safe HTML
Nonce Verification
// Generate nonce field in form
wp_nonce_field('my_action', 'my_nonce');
// Verify nonce on submission
if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
wp_die(__('Security check failed.', 'my-plugin'));
}
// AJAX nonce check
check_ajax_referer('my_plugin_nonce', 'nonce');
Capability Checks
if (!current_user_can('edit_posts')) {
wp_die(__('Insufficient permissions.', 'my-plugin'));
}
// REST API permission callback
'permission_callback' => fn() => current_user_can('edit_post', $id)
Commands Reference
# Development
npm run build # Build blocks/assets
npm run start # Watch mode for blocks
composer install # PHP dependencies
# Testing
./vendor/bin/phpunit # Run tests
./vendor/bin/phpunit --coverage-html coverage # Coverage report
# Code Quality
./vendor/bin/phpcs # PHP CodeSniffer (WPCS)
./vendor/bin/phpcbf # Auto-fix coding standards
./vendor/bin/phpstan analyse # Static analysis
# WP-CLI essentials
wp plugin activate my-plugin # Activate plugin
wp plugin list --status=active # List active plugins
wp theme activate my-theme # Activate theme
wp db export backup.sql # Database backup
wp post list --post_type=book # List posts
wp cache flush # Clear object cache
wp transient delete --all # Clear transients
wp cron event run --all # Run scheduled events
wp rewrite flush # Flush rewrite rules
Custom WP-CLI Command
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('mycommand', MyPlugin\CLI\MyCommand::class);
}
Advanced Topics
For detailed patterns and full implementation examples, see:
- references/patterns.md -- Custom post types, taxonomies, meta boxes, Gutenberg blocks, WooCommerce integration, database operations, testing, caching, performance
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?