wordpress

WordPress Framework Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "wordpress" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-wordpress

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 /**

  • 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_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 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

  • WordPress Developer Resources

  • Plugin Developer Handbook

  • Theme Developer Handbook

  • REST API Handbook

  • Block Editor Handbook

  • WordPress Coding Standards

  • WP-CLI Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

web-artifacts-builder

No summary provided by upstream source.

Repository SourceNeeds Review