| name | wordpress |
| description | 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.
|
| license | MIT |
| metadata | {"author":"samuel","version":"1.0","category":"framework","language":"php","extensions":".php"} |
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_scripts hook
- Use text domains and
__() / _e() for all user-facing strings
- Set
show_in_rest => true for post types and taxonomies that need Gutenberg/REST support
- Use
register_post_meta() to expose meta fields in the REST API
- Always include
uninstall.php or register_uninstall_hook() for cleanup
Anti-Patterns
- Do not use
query_posts() (use WP_Query or get_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/$_POST directly 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
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();
();
();
}
}
Hooks and Filters
Common Hook Patterns
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']);
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, ], , );
(, , );
= (, , );
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(),
],
[
=> ::,
=> [, ],
=> [, ],
],
]);
}
{
= ([
=> ,
=> ->() ?? ,
=> ->() ?? ,
]);
= (fn() => ->(), ->posts);
= (, );
->(, ->found_posts);
->(, ->max_num_pages);
;
}
{ ; }
{ (); }
}
REST API conventions:
- Extend
WP_REST_Controller for full CRUD endpoints
- Always define
permission_callback (use __return_true for truly public)
- Sanitize input parameters with
sanitize_callback in args
- Return
WP_Error for 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_block_type(MY_PLUGIN_PATH . 'blocks/my-block');
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 },
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>
);
},
: ,
});
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);
echo esc_attr($attribute);
echo esc_url($url);
echo esc_js($script);
echo wp_kses_post($content);
Nonce Verification
wp_nonce_field('my_action', 'my_nonce');
if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
wp_die(__('Security check failed.', 'my-plugin'));
}
check_ajax_referer('my_plugin_nonce', 'nonce');
Capability Checks
if (!current_user_can('edit_posts')) {
wp_die(__('Insufficient permissions.', 'my-plugin'));
}
'permission_callback' => fn() => current_user_can('edit_post', $id)
Commands Reference
npm run build
npm run start
composer install
./vendor/bin/phpunit
./vendor/bin/phpunit --coverage-html coverage
./vendor/bin/phpcs
./vendor/bin/phpcbf
./vendor/bin/phpstan analyse
wp plugin activate my-plugin
wp plugin list --status=active
wp theme activate my-theme
wp db export backup.sql
wp post list --post_type=book
wp cache flush
wp transient delete --all
wp cron event run --all
wp rewrite flush
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