# 2630 Wordpress Development Ecosystem 2025 01 30 Eb45a0d9

> WordPress Development Ecosystem Research

- Skill: `tools-only/2630-wordpress-development-ecosystem-2025-01-30-eb45a0d9` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/2630-wordpress-development-ecosystem-2025-01-30-eb45a0d9`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/2630-wordpress-development-ecosystem-2025-01-30-eb45a0d9/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tools-only/2630-wordpress-development-ecosystem-2025-01-30-eb45a0d9

---

# WordPress Development Ecosystem Research

**Research Date:** January 30, 2025
**Focus:** WordPress 6.x Development Practices & Skills Library Architecture
**Scope:** Plugin Development, Theme Development, Modern Tooling, Testing, Security

---

## Executive Summary

WordPress continues to evolve rapidly with WordPress 6.7 (released November 12, 2024, codenamed "Rollins") representing the current stable release. The ecosystem has shifted significantly toward **Block-based development**, **Full Site Editing (FSE)**, and **modern PHP practices** (PHP 8.3 recommended). Key architectural changes include the maturation of the Block Editor (Gutenberg), theme.json for centralized styling, and enhanced REST API capabilities.

**Critical Version Requirements:**
- **WordPress:** 6.7 (current), 6.4+ recommended for FSE features
- **PHP:** 8.3 recommended (7.4 minimum, PHP 7.0-7.1 support dropped)
- **MySQL:** 5.7+ or MariaDB 10.3+
- **Node.js:** 18.x+ for modern build tooling

**Recommended Skills Library Structure:** 5 distinct skills covering (1) Plugin Fundamentals, (2) Block Editor & FSE, (3) Security & Data Validation, (4) Testing & Quality, and (5) Advanced WordPress Architecture.

---

## 1. Latest WordPress Version & PHP Requirements

### WordPress 6.7 "Rollins" (November 12, 2024)

**Major Features:**
- **Full Site Editing Maturity:** FSE is no longer beta (stabilized in 6.2, refined in 6.4-6.7)
- **Block Bindings API:** Connect block attributes to dynamic data sources
- **Improved Block Interactivity API:** Enhanced client-side interactions without React
- **Performance Improvements:** Faster asset loading, optimized query performance
- **Enhanced Site Editor:** Improved template editing workflow

**PHP Version Support:**

| PHP Version | Status | WordPress 6.7 Support |
|-------------|--------|----------------------|
| 7.0-7.1     | EOL    | ❌ Dropped Support   |
| 7.2-7.4     | EOL    | ✅ Supported (minimum: 7.4) |
| 8.0-8.2     | Security/EOL | ✅ Compatible with exceptions |
| 8.3         | Active | ✅ **Recommended** (best performance) |
| 8.4         | Candidate | ⚠️ Beta support (testing phase) |

**Key Changes in WordPress 6.x Series:**
- **6.0-6.2:** FSE foundation, block locking, template editing
- **6.3-6.4:** Style variations, design tools, pattern improvements
- **6.5-6.6:** Font library, block hooks, interactivity API refinements
- **6.7:** Performance optimizations, block bindings, developer experience improvements

**Official Documentation:**
- PHP Compatibility: https://make.wordpress.org/core/handbook/references/php-compatibility-and-wordpress-versions/
- Server Requirements: https://wordpress.org/about/requirements/

---

## 2. Plugin Development: Modern Architecture & Best Practices

### Modern Plugin Structure (2024-2025)

**Recommended Directory Structure:**
```
my-plugin/
├── my-plugin.php              # Main plugin file (metadata header)
├── composer.json               # Dependency management (REQUIRED)
├── package.json                # npm dependencies (if using build tools)
├── includes/                   # Core business logic (PSR-4 autoloaded)
│   ├── Core.php               # Plugin bootstrap/loader class
│   ├── Admin/                 # Admin-specific functionality
│   ├── Frontend/              # Public-facing functionality
│   └── API/                   # REST API endpoints
├── assets/                     # CSS, JS, images
│   ├── css/
│   ├── js/
│   └── images/
├── blocks/                     # Custom Gutenberg blocks
├── templates/                  # PHP template files
├── languages/                  # Translation files
├── tests/                      # PHPUnit tests
│   ├── unit/
│   ├── integration/
│   └── bootstrap.php
├── .phpcs.xml.dist            # PHP_CodeSniffer config (WPCS)
├── .editorconfig              # Editor configuration
├── .gitignore
└── README.md
```

**Main Plugin File Example:**
```php
<?php
/**
 * Plugin Name: Modern WordPress Plugin
 * Plugin URI: https://example.com/my-plugin
 * Description: Modern plugin following WordPress 6.x best practices
 * Version: 1.0.0
 * Requires at least: 6.4
 * Requires PHP: 8.1
 * Author: Your Name
 * Author URI: https://example.com
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: my-plugin
 * Domain Path: /languages
 */

// Security: Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

// Define plugin constants
define( 'MY_PLUGIN_VERSION', '1.0.0' );
define( 'MY_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
define( 'MY_PLUGIN_URL', plugin_dir_url( __FILE__ ) );

// Composer autoloader
if ( file_exists( MY_PLUGIN_PATH . 'vendor/autoload.php' ) ) {
    require_once MY_PLUGIN_PATH . 'vendor/autoload.php';
}

// Initialize plugin on plugins_loaded hook
add_action( 'plugins_loaded', 'my_plugin_init' );

function my_plugin_init() {
    // Initialize core plugin class
    if ( class_exists( 'MyPlugin\\Core' ) ) {
        $plugin = new MyPlugin\\Core();
        $plugin->run();
    }
}

// Activation hook
register_activation_hook( __FILE__, 'my_plugin_activate' );
function my_plugin_activate() {
    // Run activation tasks (flush rewrite rules, create tables, set default options)
    if ( class_exists( 'MyPlugin\\Activation' ) ) {
        MyPlugin\\Activation::activate();
    }
}

// Deactivation hook
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );
function my_plugin_deactivate() {
    // Cleanup tasks (flush rewrite rules, clear scheduled events)
    if ( class_exists( 'MyPlugin\\Deactivation' ) ) {
        MyPlugin\\Deactivation::deactivate();
    }
}
```

**Core Plugin Class (OOP Singleton Pattern):**
```php
<?php
namespace MyPlugin;

class Core {
    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->load_dependencies();
        $this->define_hooks();
    }

    private function load_dependencies() {
        // Load required classes/files
    }

    private function define_hooks() {
        // Register WordPress hooks
        add_action( 'init', [ $this, 'on_init' ] );
        add_action( 'admin_menu', [ $this, 'register_admin_menu' ] );
        add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
    }

    public function run() {
        // Start the plugin execution
    }
}
```

### Plugin API Fundamentals

**1. Actions vs. Filters**

| Aspect | Actions | Filters |
|--------|---------|---------|
| Purpose | Execute code at specific points | Modify data before use/output |
| Return Value | Returns nothing (void) | **Must return value** |
| Example Use | Send emails, log events, register CPTs | Modify post content, filter queries |
| Common Pattern | `do_action()` / `add_action()` | `apply_filters()` / `add_filter()` |

**Action Example:**
```php
// Register custom post type on 'init' action
add_action( 'init', 'register_custom_post_type' );
function register_custom_post_type() {
    register_post_type( 'book', [
        'labels' => [
            'name' => __( 'Books', 'my-plugin' ),
            'singular_name' => __( 'Book', 'my-plugin' ),
        ],
        'public' => true,
        'has_archive' => true,
        'supports' => [ 'title', 'editor', 'thumbnail' ],
        'show_in_rest' => true, // Enable block editor
    ]);
}
```

**Filter Example:**
```php
// Modify post content with custom message
add_filter( 'the_content', 'add_reading_time' );
function add_reading_time( $content ) {
    if ( is_single() && in_the_loop() && is_main_query() ) {
        $word_count = str_word_count( strip_tags( $content ) );
        $reading_time = ceil( $word_count / 200 ); // 200 words/min

        $message = sprintf(
            '<p class="reading-time">Estimated reading time: %d min</p>',
            $reading_time
        );

        $content = $message . $content; // Prepend message
    }

    return $content; // MUST return modified content
}
```

**2. Hook Priority and Execution Order**

```php
// Priority: 1-999 (default: 10)
// Lower numbers = earlier execution, higher numbers = later execution

add_action( 'init', 'my_early_function', 5 );  // Runs first
add_action( 'init', 'my_normal_function' );     // Priority 10 (default)
add_action( 'init', 'my_late_function', 20 );   // Runs last

// Remove hooks
remove_action( 'init', 'my_normal_function', 10 );
remove_filter( 'the_content', 'wpautop' ); // Remove auto-paragraph formatting
```

**3. Creating Custom Hooks**

```php
// In your plugin code - create custom action hook
function my_plugin_process_order( $order_id ) {
    // Process order logic...

    // Allow other plugins/themes to hook into this point
    do_action( 'my_plugin_order_processed', $order_id, $order_data );
}

// Other developers can now hook into your plugin:
add_action( 'my_plugin_order_processed', 'send_order_notification', 10, 2 );
function send_order_notification( $order_id, $order_data ) {
    // Send email notification
}

// Custom filter hook
function my_plugin_get_price( $product_id ) {
    $price = get_post_meta( $product_id, '_price', true );

    // Allow price modification
    return apply_filters( 'my_plugin_product_price', $price, $product_id );
}
```

**Common Core Hooks:**
- **init:** Register post types, taxonomies, rewrite rules
- **plugins_loaded:** Initialize plugin (after all plugins loaded)
- **wp_enqueue_scripts:** Enqueue frontend CSS/JS
- **admin_enqueue_scripts:** Enqueue admin CSS/JS
- **wp_head:** Add code to <head> section
- **wp_footer:** Add code before </body>
- **save_post:** Runs when post is saved/updated
- **pre_get_posts:** Modify WP_Query before execution

### WordPress Coding Standards (WPCS)

**Installation via Composer:**
```bash
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev wp-coding-standards/wpcs:"^3.0"
composer require --dev phpcompatibility/phpcompatibility-wp:"*"
```

**.phpcs.xml.dist Configuration:**
```xml
<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards">
    <description>Custom ruleset for WordPress plugin</description>

    <!-- Check all PHP files -->
    <file>./includes</file>
    <file>./my-plugin.php</file>

    <!-- Exclude vendor and node_modules -->
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/node_modules/*</exclude-pattern>

    <!-- Use WordPress-Extra rules -->
    <rule ref="WordPress-Extra">
        <!-- Allow short array syntax -->
        <exclude name="Generic.Arrays.DisallowShortArraySyntax"/>
    </rule>

    <!-- Check for PHP cross-version compatibility -->
    <config name="testVersion" value="8.1-"/>

    <!-- Show progress -->
    <arg value="ps"/>
    <arg name="colors"/>
    <arg name="extensions" value="php"/>
</ruleset>
```

**Key Coding Rules:**
- **Indentation:** Tabs (not spaces)
- **Braces:** Opening brace on same line for control structures
- **Yoda Conditions:** `if ( true === $value )` instead of `if ( $value === true )`
- **Naming:** Snake_case for functions/variables, PascalCase for classes
- **Documentation:** PHPDoc blocks for all functions/classes

**Running PHPCS:**
```bash
# Check coding standards
vendor/bin/phpcs

# Auto-fix fixable issues
vendor/bin/phpcbf

# Check specific file
vendor/bin/phpcs includes/Core.php
```

### Database Interactions

**1. Using $wpdb Global Object:**
```php
global $wpdb;

// Prepare queries to prevent SQL injection
$user_id = 42;
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->posts} WHERE post_author = %d",
        $user_id
    )
);

// Insert data
$wpdb->insert(
    $wpdb->prefix . 'my_custom_table',
    [
        'column1' => 'value1',
        'column2' => 123,
        'created_at' => current_time( 'mysql' ),
    ],
    [ '%s', '%d', '%s' ] // Data format
);

// Update data
$wpdb->update(
    $wpdb->prefix . 'my_custom_table',
    [ 'column1' => 'new_value' ], // Data to update
    [ 'id' => 5 ],                 // WHERE condition
    [ '%s' ],                       // Data format
    [ '%d' ]                        // WHERE format
);

// Delete data
$wpdb->delete(
    $wpdb->prefix . 'my_custom_table',
    [ 'id' => 5 ],
    [ '%d' ]
);
```

**2. Creating Custom Tables:**
```php
function my_plugin_create_tables() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'my_custom_table';
    $charset_collate = $wpdb->get_charset_collate();

    // IMPORTANT: Specific SQL formatting required for dbDelta()
    $sql = "CREATE TABLE $table_name (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        user_id bigint(20) unsigned NOT NULL,
        title varchar(255) NOT NULL,
        content longtext,
        status varchar(20) DEFAULT 'draft',
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        KEY user_id (user_id),
        KEY status (status)
    ) $charset_collate;";

    // dbDelta() intelligently creates or updates tables
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );

    // Store database version for future migrations
    add_option( 'my_plugin_db_version', '1.0.0' );
}

// Run on plugin activation
register_activation_hook( __FILE__, 'my_plugin_create_tables' );
```

**Best Practices:**
- ✅ Always use `$wpdb->prepare()` for dynamic queries
- ✅ Use `$wpdb->prefix` (never hard-code `wp_`)
- ✅ Use `$wpdb->get_charset_collate()` for correct character encoding
- ✅ Use `dbDelta()` for table creation/updates (handles schema changes)
- ✅ Store schema version in options table for migrations
- ⚠️ Consider using existing tables (post_meta, options) before creating custom tables

### Settings API and Options Framework

**1. Options API (Simple Key-Value Storage):**
```php
// Add option
add_option( 'my_plugin_setting', 'default_value' );

// Get option
$value = get_option( 'my_plugin_setting', 'default_if_not_exists' );

// Update option
update_option( 'my_plugin_setting', 'new_value' );

// Delete option
delete_option( 'my_plugin_setting' );
```

**2. Settings API (Structured Admin Pages):**
```php
// Register settings on admin_init
add_action( 'admin_init', 'my_plugin_register_settings' );
function my_plugin_register_settings() {
    // Register setting
    register_setting(
        'my_plugin_options',           // Option group
        'my_plugin_settings',           // Option name
        [
            'type' => 'array',
            'sanitize_callback' => 'my_plugin_sanitize_settings',
            'default' => [],
        ]
    );

    // Add settings section
    add_settings_section(
        'my_plugin_main_section',       // Section ID
        __( 'Main Settings', 'my-plugin' ),
        'my_plugin_section_callback',   // Callback for section description
        'my_plugin_settings_page'       // Page slug
    );

    // Add settings field
    add_settings_field(
        'api_key',                      // Field ID
        __( 'API Key', 'my-plugin' ),
        'my_plugin_api_key_callback',   // Callback to render field
        'my_plugin_settings_page',      // Page slug
        'my_plugin_main_section',       // Section ID
        [ 'label_for' => 'api_key' ]    // Extra arguments
    );
}

// Sanitize callback
function my_plugin_sanitize_settings( $input ) {
    $sanitized = [];

    if ( isset( $input['api_key'] ) ) {
        $sanitized['api_key'] = sanitize_text_field( $input['api_key'] );
    }

    return $sanitized;
}

// Field render callback
function my_plugin_api_key_callback( $args ) {
    $options = get_option( 'my_plugin_settings' );
    $value = isset( $options['api_key'] ) ? $options['api_key'] : '';
    ?>
    <input
        type="text"
        id="<?php echo esc_attr( $args['label_for'] ); ?>"
        name="my_plugin_settings[api_key]"
        value="<?php echo esc_attr( $value ); ?>"
        class="regular-text"
    />
    <?php
}

// Settings page template
function my_plugin_settings_page() {
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            settings_fields( 'my_plugin_options' );
            do_settings_sections( 'my_plugin_settings_page' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}
```

**Modern Alternatives (2024):**
- **WordPress Settings Framework:** https://github.com/iconicwp/WordPress-Settings-Framework
- **Carbon Fields:** https://carbonfields.net/ (React-based UI)
- **REST API Approach:** Build settings UI with React/Vue, save via REST endpoints

---

## 3. Security Best Practices

### The Three-Layer Security Model

**Golden Rule:** "Sanitize on input, validate for logic, escape on output."

### 1. Nonces (Numbers Used Once)

**Purpose:** Prevent CSRF (Cross-Site Request Forgery) attacks

```php
// Create nonce for form
<form method="post">
    <?php wp_nonce_field( 'my_action', 'my_nonce_field' ); ?>
    <input type="text" name="user_data" />
    <button type="submit">Submit</button>
</form>

// Verify nonce on form submission
function handle_form_submission() {
    // Check if nonce is set and valid
    if ( ! isset( $_POST['my_nonce_field'] ) ||
         ! wp_verify_nonce( $_POST['my_nonce_field'], 'my_action' ) ) {
        wp_die( 'Security check failed' );
    }

    // Process form data
    $user_data = sanitize_text_field( $_POST['user_data'] );
    // ...
}

// Nonce for URLs
$url = wp_nonce_url(
    admin_url( 'admin.php?action=delete_item&id=5' ),
    'delete_item_5',
    'delete_nonce'
);

// Verify URL nonce
if ( ! isset( $_GET['delete_nonce'] ) ||
     ! wp_verify_nonce( $_GET['delete_nonce'], 'delete_item_5' ) ) {
    wp_die( 'Invalid security token' );
}

// Nonce for AJAX
wp_localize_script( 'my-ajax-script', 'myAjax', [
    'ajaxurl' => admin_url( 'admin-ajax.php' ),
    'nonce' => wp_create_nonce( 'my_ajax_nonce' ),
]);

// Verify AJAX nonce
add_action( 'wp_ajax_my_action', 'handle_ajax_request' );
function handle_ajax_request() {
    check_ajax_referer( 'my_ajax_nonce', 'nonce' );

    // Process AJAX request
    wp_send_json_success( [ 'message' => 'Success!' ] );
}
```

### 2. Sanitization (Input Cleaning)

**Purpose:** Remove unwanted/dangerous characters from input

```php
// Text input
$clean_text = sanitize_text_field( $_POST['username'] );

// Email
$clean_email = sanitize_email( $_POST['email'] );

// URL
$clean_url = esc_url_raw( $_POST['website'] );

// Filename
$clean_filename = sanitize_file_name( $_FILES['upload']['name'] );

// HTML content (allows safe HTML tags)
$clean_html = wp_kses_post( $_POST['content'] );

// HTML with custom allowed tags
$allowed_html = [
    'a' => [ 'href' => [], 'title' => [] ],
    'strong' => [],
    'em' => [],
];
$clean_custom_html = wp_kses( $_POST['content'], $allowed_html );

// Integer
$clean_int = absint( $_POST['user_id'] ); // Absolute integer

// Array of text fields
$clean_array = array_map( 'sanitize_text_field', $_POST['items'] );
```

### 3. Validation (Logic Checks)

**Purpose:** Ensure data meets business requirements

```php
function validate_user_registration( $data ) {
    $errors = [];

    // Validate email
    if ( empty( $data['email'] ) || ! is_email( $data['email'] ) ) {
        $errors[] = 'Invalid email address';
    }

    // Validate username length
    if ( strlen( $data['username'] ) < 3 ) {
        $errors[] = 'Username must be at least 3 characters';
    }

    // Validate age (must be integer between 18-100)
    if ( ! is_numeric( $data['age'] ) || $data['age'] < 18 || $data['age'] > 100 ) {
        $errors[] = 'Invalid age';
    }

    // Validate required field
    if ( empty( $data['terms_accepted'] ) ) {
        $errors[] = 'You must accept the terms';
    }

    return empty( $errors ) ? true : $errors;
}
```

### 4. Escaping (Output Protection)

**Purpose:** Prevent XSS (Cross-Site Scripting) attacks

```php
// HTML content
echo esc_html( $user_input ); // Converts < > & " ' to HTML entities

// HTML attributes
<input type="text" value="<?php echo esc_attr( $value ); ?>" />

// URLs
<a href="<?php echo esc_url( $link ); ?>">Link</a>

// JavaScript strings
<script>
    var message = '<?php echo esc_js( $message ); ?>';
</script>

// Textarea content
<textarea><?php echo esc_textarea( $content ); ?></textarea>

// Internationalized text (with HTML allowed)
echo wp_kses_post( __( 'Welcome <strong>user</strong>!', 'my-plugin' ) );

// Internationalized text (no HTML)
echo esc_html__( 'Welcome user!', 'my-plugin' );

// URLs with translation
echo esc_url( __( 'https://example.com/page', 'my-plugin' ) );
```

### 5. Capability Checks (Authorization)

```php
// Check if user is logged in
if ( ! is_user_logged_in() ) {
    wp_die( 'You must be logged in' );
}

// Check user capability
if ( ! current_user_can( 'edit_posts' ) ) {
    wp_die( 'You do not have permission' );
}

// Check capability for specific post
if ( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_die( 'You cannot edit this post' );
}

// Common capabilities:
// - read: Read-only access
// - edit_posts: Create/edit own posts
// - edit_published_posts: Edit published posts
// - delete_posts: Delete posts
// - manage_options: Access settings (admin)
// - upload_files: Upload media
```

**Complete Security Example:**
```php
add_action( 'admin_post_save_custom_data', 'handle_custom_form' );
function handle_custom_form() {
    // 1. Verify nonce
    if ( ! isset( $_POST['custom_nonce'] ) ||
         ! wp_verify_nonce( $_POST['custom_nonce'], 'save_custom_data' ) ) {
        wp_die( 'Security check failed' );
    }

    // 2. Check user capability
    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_die( 'You do not have permission' );
    }

    // 3. Sanitize input
    $title = sanitize_text_field( $_POST['title'] );
    $email = sanitize_email( $_POST['email'] );
    $age = absint( $_POST['age'] );

    // 4. Validate data
    $errors = [];
    if ( empty( $title ) ) {
        $errors[] = 'Title is required';
    }
    if ( ! is_email( $email ) ) {
        $errors[] = 'Invalid email';
    }
    if ( $age < 18 ) {
        $errors[] = 'Must be 18 or older';
    }

    if ( ! empty( $errors ) ) {
        wp_die( implode( '<br>', $errors ) );
    }

    // 5. Process data (with escaped output if needed)
    $result = save_to_database( $title, $email, $age );

    // 6. Redirect with success message
    wp_redirect( add_query_arg( 'message', 'success', wp_get_referer() ) );
    exit;
}
```

---

## 4. Block Editor (Gutenberg) & Full Site Editing

### Current State (2024-2025)

- **FSE Status:** Production-ready (no longer beta since WordPress 6.2)
- **Block Themes:** Preferred approach for new themes (HTML templates + theme.json)
- **Classic Themes:** Still supported but considered legacy
- **Hybrid Approach:** Classic themes can add block support incrementally

### Block Theme Architecture

**File Structure:**
```
my-block-theme/
├── style.css                 # Theme metadata
├── theme.json                # Global styles and settings (REQUIRED)
├── functions.php             # Theme setup (optional)
├── templates/                # HTML block templates
│   ├── index.html           # Fallback template (REQUIRED)
│   ├── home.html
│   ├── single.html
│   ├── single-product.html  # Custom post type template
│   ├── page.html
│   └── 404.html
├── parts/                    # Template parts (header, footer, etc.)
│   ├── header.html
│   ├── footer.html
│   └── sidebar.html
├── patterns/                 # Block patterns (reusable designs)
│   ├── hero.php
│   └── call-to-action.php
├── assets/
│   ├── css/
│   └── js/
└── languages/
```

### theme.json (Global Styles)

**Purpose:** Centralized configuration for colors, typography, spacing, layouts

```json
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "800px",
      "wideSize": "1200px"
    },
    "color": {
      "palette": [
        {
          "slug": "primary",
          "color": "#0073aa",
          "name": "Primary"
        },
        {
          "slug": "secondary",
          "color": "#005177",
          "name": "Secondary"
        },
        {
          "slug": "black",
          "color": "#000000",
          "name": "Black"
        },
        {
          "slug": "white",
          "color": "#ffffff",
          "name": "White"
        }
      ],
      "gradients": [],
      "duotone": [],
      "defaultPalette": false,
      "defaultGradients": false
    },
    "typography": {
      "fontFamilies": [
        {
          "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
          "slug": "system",
          "name": "System Font"
        }
      ],
      "fontSizes": [
        {
          "slug": "small",
          "size": "0.875rem",
          "name": "Small"
        },
        {
          "slug": "medium",
          "size": "1rem",
          "name": "Medium"
        },
        {
          "slug": "large",
          "size": "1.5rem",
          "name": "Large"
        },
        {
          "slug": "x-large",
          "size": "2rem",
          "name": "Extra Large"
        }
      ],
      "lineHeight": true,
      "dropCap": false
    },
    "spacing": {
      "units": ["px", "em", "rem", "vh", "vw", "%"],
      "padding": true,
      "margin": true,
      "blockGap": true
    },
    "border": {
      "radius": true,
      "color": true,
      "style": true,
      "width": true
    },
    "blocks": {
      "core/button": {
        "color": {
          "palette": [
            {
              "slug": "button-primary",
              "color": "#0073aa",
              "name": "Button Primary"
            }
          ]
        }
      }
    }
  },
  "styles": {
    "color": {
      "background": "#ffffff",
      "text": "#000000"
    },
    "typography": {
      "fontFamily": "var(--wp--preset--font-family--system)",
      "fontSize": "var(--wp--preset--font-size--medium)",
      "lineHeight": "1.6"
    },
    "spacing": {
      "blockGap": "1.5rem"
    },
    "elements": {
      "link": {
        "color": {
          "text": "var(--wp--preset--color--primary)"
        },
        ":hover": {
          "color": {
            "text": "var(--wp--preset--color--secondary)"
          }
        }
      },
      "h1": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--x-large)",
          "lineHeight": "1.2"
        }
      }
    },
    "blocks": {
      "core/button": {
        "color": {
          "background": "var(--wp--preset--color--primary)",
          "text": "var(--wp--preset--color--white)"
        },
        "border": {
          "radius": "4px"
        },
        ":hover": {
          "color": {
            "background": "var(--wp--preset--color--secondary)"
          }
        }
      }
    }
  },
  "customTemplates": [
    {
      "name": "page-no-sidebar",
      "title": "Page Without Sidebar",
      "postTypes": ["page"]
    },
    {
      "name": "single-product",
      "title": "Product Template",
      "postTypes": ["product"]
    }
  ],
  "templateParts": [
    {
      "name": "header",
      "title": "Header",
      "area": "header"
    },
    {
      "name": "footer",
      "title": "Footer",
      "area": "footer"
    }
  ]
}
```

**Key theme.json Features:**
- **Version 3** (WordPress 6.6+): Latest schema version
- **Appearance Tools:** Enable border, spacing, typography controls
- **Block-Specific Settings:** Customize individual blocks
- **Custom Templates:** Register templates for specific post types
- **CSS Custom Properties:** Auto-generated CSS variables (e.g., `var(--wp--preset--color--primary)`)

### Block Template Example

**templates/single.html:**
```html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
    <!-- wp:post-title {"level":1} /-->

    <!-- wp:post-featured-image /-->

    <!-- wp:post-content {"layout":{"type":"constrained"}} /-->

    <!-- wp:post-date /-->

    <!-- wp:post-author /-->

    <!-- wp:comments /-->
</div>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
```

### Custom Block Development (PHP Registration)

**Modern Approach (block.json metadata):**

```php
// Register block in functions.php or plugin
add_action( 'init', 'register_custom_block' );
function register_custom_block() {
    register_block_type( __DIR__ . '/blocks/testimonial' );
}
```

**blocks/testimonial/block.json:**
```json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-plugin/testimonial",
  "title": "Testimonial",
  "category": "widgets",
  "icon": "format-quote",
  "description": "Display a customer testimonial.",
  "keywords": ["quote", "review", "testimonial"],
  "version": "1.0.0",
  "textdomain": "my-plugin",
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": ".testimonial-content"
    },
    "author": {
      "type": "string",
      "default": ""
    },
    "role": {
      "type": "string",
      "default": ""
    }
  },
  "supports": {
    "html": false,
    "align": true,
    "color": {
      "background": true,
      "text": true
    },
    "spacing": {
      "padding": true,
      "margin": true
    }
  },
  "editorScript": "file:./index.js",
  "editorStyle": "file:./editor.css",
  "style": "file:./style.css",
  "render": "file:./render.php"
}
```

**blocks/testimonial/render.php:**
```php
<?php
/**
 * Testimonial block template
 *
 * @var array    $attributes Block attributes
 * @var string   $content    Block content
 * @var WP_Block $block      Block instance
 */

$content = $attributes['content'] ?? '';
$author = $attributes['author'] ?? '';
$role = $attributes['role'] ?? '';

$wrapper_attributes = get_block_wrapper_attributes([
    'class' => 'testimonial-block',
]);
?>

<div <?php echo $wrapper_attributes; ?>>
    <blockquote class="testimonial-content">
        <?php echo wp_kses_post( $content ); ?>
    </blockquote>

    <?php if ( $author ) : ?>
        <cite class="testimonial-author">
            <span class="author-name"><?php echo esc_html( $author ); ?></span>
            <?php if ( $role ) : ?>
                <span class="author-role"><?php echo esc_html( $role ); ?></span>
            <?php endif; ?>
        </cite>
    <?php endif; ?>
</div>
```

### Custom Post Types & Taxonomies with Block Support

```php
// Register custom post type with block editor support
add_action( 'init', 'register_book_post_type' );
function register_book_post_type() {
    register_post_type( 'book', [
        'labels' => [
            'name' => __( 'Books', 'my-plugin' ),
            'singular_name' => __( 'Book', 'my-plugin' ),
        ],
        'public' => true,
        'has_archive' => true,
        'rewrite' => [ 'slug' => 'books' ],
        'supports' => [
            'title',
            'editor',        // Block editor
            'thumbnail',
            'custom-fields',
            'excerpt',
        ],
        'show_in_rest' => true,  // REQUIRED for block editor
        'rest_base' => 'books',
        'menu_icon' => 'dashicons-book',
        'template' => [          // Default block template
            [ 'core/paragraph', [
                'placeholder' => 'Enter book description...',
            ]],
            [ 'core/image' ],
            [ 'my-plugin/book-details' ],
        ],
        'template_lock' => 'insert', // Prevent adding/removing blocks
    ]);
}

// Register custom taxonomy
add_action( 'init', 'register_book_genre_taxonomy' );
function register_book_genre_taxonomy() {
    register_taxonomy( 'genre', 'book', [
        'labels' => [
            'name' => __( 'Genres', 'my-plugin' ),
            'singular_name' => __( 'Genre', 'my-plugin' ),
        ],
        'hierarchical' => true, // Like categories
        'show_in_rest' => true, // REQUIRED for block editor
        'rest_base' => 'genres',
        'rewrite' => [ 'slug' => 'genre' ],
    ]);
}
```

**Template Assignment in theme.json:**
```json
{
  "customTemplates": [
    {
      "name": "single-book",
      "title": "Book Template",
      "postTypes": ["book"]
    }
  ]
}
```

---

## 5. REST API Development

### Registering Custom Endpoints

```php
add_action( 'rest_api_init', 'register_custom_rest_routes' );
function register_custom_rest_routes() {
    // Route: GET /wp-json/myplugin/v1/items
    register_rest_route( 'myplugin/v1', '/items', [
        'methods'  => 'GET',
        'callback' => 'get_items_callback',
        'permission_callback' => '__return_true', // Public endpoint
    ]);

    // Route: GET /wp-json/myplugin/v1/items/(?P<id>\d+)
    register_rest_route( 'myplugin/v1', '/items/(?P<id>\d+)', [
        'methods'  => 'GET',
        'callback' => 'get_item_callback',
        'permission_callback' => '__return_true',
        'args' => [
            'id' => [
                'validate_callback' => function( $param ) {
                    return is_numeric( $param );
                },
            ],
        ],
    ]);

    // Route: POST /wp-json/myplugin/v1/items (authenticated)
    register_rest_route( 'myplugin/v1', '/items', [
        'methods'  => 'POST',
        'callback' => 'create_item_callback',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
        'args' => [
            'title' => [
                'required' => true,
                'validate_callback' => function( $param ) {
                    return is_string( $param ) && strlen( $param ) > 0;
                },
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'content' => [
                'required' => false,
                'sanitize_callback' => 'wp_kses_post',
            ],
        ],
    ]);
}

// Callback functions
function get_items_callback( $request ) {
    // Query parameters
    $per_page = $request->get_param( 'per_page' ) ?: 10;
    $page = $request->get_param( 'page' ) ?: 1;

    // Fetch data
    $items = [
        [ 'id' => 1, 'title' => 'Item 1' ],
        [ 'id' => 2, 'title' => 'Item 2' ],
    ];

    return rest_ensure_response( $items );
}

function get_item_callback( $request ) {
    $id = $request->get_param( 'id' );

    $item = [ 'id' => $id, 'title' => "Item $id" ];

    if ( ! $item ) {
        return new WP_Error( 'not_found', 'Item not found', [ 'status' => 404 ] );
    }

    return rest_ensure_response( $item );
}

function create_item_callback( $request ) {
    $title = $request->get_param( 'title' );
    $content = $request->get_param( 'content' );

    // Insert into database
    global $wpdb;
    $result = $wpdb->insert(
        $wpdb->prefix . 'my_items',
        [
            'title' => $title,
            'content' => $content,
            'created_at' => current_time( 'mysql' ),
        ],
        [ '%s', '%s', '%s' ]
    );

    if ( ! $result ) {
        return new WP_Error( 'creation_failed', 'Failed to create item', [ 'status' => 500 ] );
    }

    $item_id = $wpdb->insert_id;

    return rest_ensure_response([
        'id' => $item_id,
        'title' => $title,
        'message' => 'Item created successfully',
    ], 201 );
}
```

### REST API Best Practices

1. **Use Proper Namespacing:** `myplugin/v1` (allows versioning)
2. **Implement Permission Callbacks:** Always define `permission_callback`
3. **Validate & Sanitize:** Use `validate_callback` and `sanitize_callback`
4. **Return Proper HTTP Codes:** 200 (success), 201 (created), 400 (bad request), 401 (unauthorized), 404 (not found), 500 (error)
5. **Use Controller Pattern:** For complex endpoints, use dedicated controller classes
6. **Implement Pagination:** Support `?page=2&per_page=10` query parameters
7. **Enable HTTPS:** Always use HTTPS for API endpoints
8. **Use Authentication:** Application passwords, OAuth, or JWT for sensitive endpoints

**Testing REST API:**
```bash
# GET request
curl https://example.com/wp-json/myplugin/v1/items

# POST request with authentication
curl -X POST https://example.com/wp-json/myplugin/v1/items \
  -H "Content-Type: application/json" \
  -u username:application_password \
  -d '{"title":"New Item","content":"Item content"}'

# Using Postman or Insomnia for interactive testing
```

---

## 6. Modern Development Tools & Workflow

### Local Development Environments

**1. wp-env (Official WordPress Tool)**

**Features:**
- Docker-based (no manual configuration)
- Includes WordPress, MySQL, PHP, Composer, PHPUnit, WP-CLI
- Fast setup (one command)
- Customizable via `.wp-env.json`

**Setup:**
```bash
# Install globally
npm install -g @wordpress/env

# Start WordPress environment (in any directory)
wp-env start

# Access:
# - Frontend: http://localhost:8888
# - Admin: http://localhost:8888/wp-admin (admin/password)

# Stop environment
wp-env stop

# Clean environment (reset database)
wp-env clean
```

**.wp-env.json Configuration:**
```json
{
  "core": "WordPress/WordPress#6.7",
  "phpVersion": "8.3",
  "plugins": [
    "./my-plugin",
    "https://downloads.wordpress.org/plugin/woocommerce.latest.zip"
  ],
  "themes": [
    "./my-theme"
  ],
  "config": {
    "WP_DEBUG": true,
    "WP_DEBUG_LOG": true,
    "WP_DEBUG_DISPLAY": false
  },
  "env": {
    "development": {
      "port": 8888
    },
    "tests": {
      "port": 8889
    }
  }
}
```

**2. LocalWP (Local by Flywheel)**

**Features:**
- GUI application (no command line required)
- One-click WordPress installation
- Easy PHP/MySQL version switching
- Built-in SSL, MailHog (email testing)
- Push to Flywheel/WP Engine hosting

**Best For:** Designers, non-developers, quick prototyping

**3. Docker Compose (Custom Setup)**

**docker-compose.yml Example:**
```yaml
version: '3.8'

services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8000:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DEBUG: 1
    volumes:
      - ./wp-content:/var/www/html/wp-content
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    volumes:
      - db_data:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    ports:
      - "8080:80"
    environment:
      PMA_HOST: db
      PMA_USER: wordpress
      PMA_PASSWORD: wordpress

volumes:
  db_data:
```

**Usage:**
```bash
docker-compose up -d
# Access WordPress: http://localhost:8000
# Access phpMyAdmin: http://localhost:8080
```

### WP-CLI (WordPress Command-Line Interface)

**Installation:**
```bash
# Download WP-CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

# Make executable
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

# Verify
wp --info
```

**Essential Commands:**
```bash
# Install WordPress
wp core download --locale=en_US
wp config create --dbname=wordpress --dbuser=root --dbpass=password
wp core install --url=http://localhost --title="My Site" --admin_user=admin --admin_password=password --admin_email=admin@example.com

# Plugin management
wp plugin install woocommerce --activate
wp plugin list
wp plugin update --all
wp plugin deactivate my-plugin
wp plugin delete my-plugin

# Theme management
wp theme install twentytwentyfour --activate
wp theme list
wp theme update --all

# Database operations
wp db export backup.sql
wp db import backup.sql
wp db query "SELECT * FROM wp_posts LIMIT 5"

# Search and replace (for migrations)
wp se

…(truncated)
