internal/skills/content/wordpress/SKILL.md
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.
npx skillsauth add ar4mirez/samuel wordpressInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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
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:
Consider alternatives when:
declare(strict_types=1) in all PHP filesif (!defined('ABSPATH')) { exit; }esc_html(), esc_attr(), esc_url(), wp_kses_post()sanitize_text_field(), sanitize_email(), absint()current_user_can()$wpdb->prepare() for all database queries (never concatenate)wp_enqueue_scripts hook__() / _e() for all user-facing stringsshow_in_rest => true for post types and taxonomies that need Gutenberg/REST supportregister_post_meta() to expose meta fields in the REST APIuninstall.php or register_uninstall_hook() for cleanupquery_posts() (use WP_Query or get_posts())home_url(), admin_url(), plugin_dir_url())extract() on untrusted data$_GET/$_POST directly without sanitizationmy-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
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)
WordPress resolves templates from most specific to least specific. Pattern: {type}-{slug}.php -> {type}-{id}.php -> {type}.php -> index.php
single-{post-type}-{slug} -> single-{post-type} -> single -> singular -> indexpage-{slug} -> page-{id} -> page -> singular -> indexarchive-{post-type} -> archive -> indexcategory-{slug} -> category-{id} -> category -> archive -> indextaxonomy-{tax}-{term} -> taxonomy-{tax} -> taxonomy -> archivesearch.php / 404.php -> index.php<?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();
});
<?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();
}
}
// 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);
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'),
],
]);
}
<?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:
WP_REST_Controller for full CRUD endpointspermission_callback (use __return_true for truly public)sanitize_callback in argsWP_Error for error responses with proper status codesX-WP-Total, X-WP-TotalPagesmy-plugin/v1// 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],
],
]);
{
"$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"
}
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
});
$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']);
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
// 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');
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)
# 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
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('mycommand', MyPlugin\CLI\MyCommand::class);
}
For detailed patterns and full implementation examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
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. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.
development
Vapor framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Vapor projects, or when the user mentions Vapor. Provides Fluent ORM, async Swift, routing, middleware, and server-side Swift guidelines.