WordPress Development
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}.php → single.php → singular.php → index.php |
| Page | page-{slug}.php → page-{id}.php → page.php → singular.php |
| Archive | archive-{post-type}.php → archive.php → index.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-redisorobject-cache-pro) - Page caching via Cloudflare, Varnish, or WP Rocket
- Lazy-load images and defer non-critical scripts
- Limit autoloaded options (
wp_optionswhereautoload = '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_DEBUGoff;WP_DEBUG_LOGon only if needed - Unique salts in
wp-config.php - HTTPS enforced;
FORCE_SSL_ADMINenabled - 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.