WordPress powers over 40% of the web. For PHP developers, it is both a CMS platform and a full application framework with hooks, a database abstraction layer, and a mature plugin ecosystem.

Architecture Overview

  Browser → wp-config.php → wp-load.php → wp-settings.php
                              ↓
                    Hooks (actions/filters)
                              ↓
              Theme templates + Plugin logic
                              ↓
                         MySQL database
  

WordPress follows a hook-driven architecture. Core code fires actions and filters; themes and plugins attach callbacks without modifying core files.

Local Development Setup

  # Using WP-CLI (recommended)
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar && sudo mv wp-cli.phar /usr/local/bin/wp

wp core download
wp config create --dbname=wordpress --dbuser=root --dbpass=secret
wp core install --url=localhost:8080 --title="My Site" --admin_user=admin --admin_password=secret [email protected]
wp plugin install woocommerce --activate
  

Use Local WP, Docker, or Laravel Valet for team-friendly environments.

Themes: Template Hierarchy

WordPress resolves templates in a predictable order:

Request Template tried (in order)
Single post single-{post-type}.phpsingle.phpsingular.phpindex.php
Page page-{slug}.phppage-{id}.phppage.phpsingular.php
Archive archive-{post-type}.phparchive.phpindex.php

A minimal functions.php:

  <?php
function mytheme_setup(): void {
    add_theme_support('title-tag');
    add_theme_support('post-thumbnails');
    register_nav_menus(['primary' => __('Primary Menu', 'mytheme')]);
}
add_action('after_setup_theme', 'mytheme_setup');

function mytheme_enqueue_assets(): void {
    wp_enqueue_style('mytheme-style', get_stylesheet_uri(), [], '1.0.0');
}
add_action('wp_enqueue_scripts', 'mytheme_enqueue_assets');
  

Modern themes often use block themes (theme.json) and Full Site Editing (FSE) instead of classic PHP templates.

Plugins: Hooks and Structure

  <?php
/**
 * Plugin Name: Order Notifier
 * Description: Sends email when WooCommerce orders complete.
 * Version: 1.0.0
 */

declare(strict_types=1);

if (!defined('ABSPATH')) {
    exit;
}

function order_notifier_on_complete(int $order_id): void {
    $order = wc_get_order($order_id);
    if (!$order) {
        return;
    }
    wp_mail(
        get_option('admin_email'),
        'Order completed',
        sprintf('Order #%d total: %s', $order_id, $order->get_total())
    );
}
add_action('woocommerce_order_status_completed', 'order_notifier_on_complete');
  

Actions vs Filters

  • Actions — run code at a specific point (add_action('init', ...))
  • Filters — modify data before use (add_filter('the_content', ...))

Always prefix function names (myplugin_) to avoid collisions.

Database Access

Prefer WordPress APIs over raw SQL:

  global $wpdb;

// Prepared statement (safe)
$users = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT ID, user_login FROM {$wpdb->users} WHERE user_registered > %s",
        '2024-01-01'
    )
);

// Higher-level API
$query = new WP_Query([
    'post_type'      => 'product',
    'posts_per_page' => 10,
    'meta_query'     => [
        ['key' => '_price', 'value' => 50, 'compare' => '<', 'type' => 'NUMERIC'],
    ],
]);
  

Use $wpdb->prepare() for every dynamic query. Never concatenate user input into SQL.

REST API

WordPress exposes a REST API at /wp-json/wp/v2/. Register custom routes:

  add_action('rest_api_init', function (): void {
    register_rest_route('myplugin/v1', '/stats', [
        'methods'             => 'GET',
        'callback'            => 'myplugin_get_stats',
        'permission_callback' => function (): bool {
            return current_user_can('manage_options');
        },
    ]);
});

function myplugin_get_stats(): WP_REST_Response {
    return new WP_REST_Response(['posts' => wp_count_posts()->publish], 200);
}
  

Authenticate with Application Passwords, OAuth plugins, or JWT for headless frontends (React, Next.js).

Gutenberg Blocks

  // src/blocks/cta/index.js
import { registerBlockType } from '@wordpress/blocks';
import { RichText } from '@wordpress/block-editor';

registerBlockType('myplugin/cta', {
    title: 'Call to Action',
    category: 'widgets',
    attributes: { text: { type: 'string', default: 'Learn more' } },
    edit: ({ attributes, setAttributes }) => (
        <RichText
            tagName="p"
            value={attributes.text}
            onChange={(text) => setAttributes({ text })}
        />
    ),
    save: ({ attributes }) => <p className="cta">{attributes.text}</p>,
});
  

Build with @wordpress/scripts (npm run build) for production assets.

Security Essentials

  • Escape output: esc_html(), esc_attr(), esc_url()
  • Sanitize input: sanitize_text_field(), absint()
  • Verify nonces on form submissions: wp_verify_nonce()
  • Check capabilities: current_user_can('edit_posts')
  • Keep core, themes, and plugins updated
  • Disable file editing in production: define('DISALLOW_FILE_EDIT', true);

Performance and Caching

  • Use object caching (Redis with wp-redis or object-cache-pro)
  • Page caching via Cloudflare, Varnish, or WP Rocket
  • Lazy-load images and defer non-critical scripts
  • Limit autoloaded options (wp_options where autoload = 'yes')
  • Query Monitor plugin helps find slow hooks and queries

Headless WordPress

Decouple the CMS from the frontend:

  Next.js / React  →  WP REST API or WPGraphQL  →  MySQL
  

Plugins like WPGraphQL expose flexible GraphQL schemas. Use webhooks or ISR revalidation when content changes.

Deployment Checklist

  • WP_DEBUG off; WP_DEBUG_LOG on only if needed
  • Unique salts in wp-config.php
  • HTTPS enforced; FORCE_SSL_ADMIN enabled
  • Database backups automated (UpdraftPlus, managed host)
  • Staging environment mirrors production PHP/MySQL versions
  • Composer for custom plugins; avoid committing vendor/ to theme repos

WordPress rewards developers who understand its hook system, respect its APIs, and treat plugins as proper PHP packages with tests and versioning.