WordPress Development
WordPress powers 43% of the web. Interviews for WordPress developer roles go beyond themes and plugins — they test your understanding of the architecture, hooks system, the REST API, security hardening, performance optimization, and Gutenberg block development.
WordPress Architecture
WordPress is a monolithic PHP application built on top of MySQL/MariaDB. Understanding its request lifecycle is fundamental.
The Request Lifecycle
- wp-config.php: Loads database credentials, auth salts, and constants like
WP_DEBUG. - wp-settings.php: Bootstraps WordPress — loads core files, active plugins, and the active theme.
- Plugin Loading: All active plugins are loaded (their main PHP files are
require-d). This is when plugin hooks fire. - Theme Loading: The active theme's
functions.phpruns (acts like a plugin for the theme). - Query Parsing: WordPress parses the URL using Rewrite Rules, determines the query (post, page, archive, etc.).
- Template Loading: The Template Hierarchy determines which template file renders the page.
The Template Hierarchy
WordPress uses a priority system to decide which template file to use. Interviewers love asking this.
// For a single blog post (post type "post"):
// 1. single-post-{slug}.php (most specific)
// 2. single-post.php
// 3. single.php
// 4. singular.php
// 5. index.php (ultimate fallback)
// For a custom post type "product":
// 1. single-product.php
// 2. single.php
// 3. singular.php
// 4. index.php
// For a category archive:
// 1. category-{slug}.php
// 2. category-{id}.php
// 3. category.php
// 4. archive.php
// 5. index.phpThe Hook System — WordPress's Core Pattern
This is the most important concept in WordPress development. The entire plugin/theme ecosystem is built on hooks. It's essentially the Observer pattern.
Actions vs Filters
// ACTIONS — "Do something at this point in execution"
// They don't return anything; they execute side effects.
// Register a callback on the 'init' action hook
add_action('init', 'my_custom_post_types');
function my_custom_post_types() {
register_post_type('product', [
'label' => 'Products',
'public' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'has_archive' => true,
]);
}
// Hook into 'wp_enqueue_scripts' to load CSS/JS
add_action('wp_enqueue_scripts', function() {
wp_enqueue_style('main-css', get_stylesheet_uri(), [], '1.0.0');
wp_enqueue_script('main-js', get_template_directory_uri() . '/js/app.js',
['jquery'], '1.0.0', true); // true = load in footer
});
// Common actions to know:
// init — WordPress finished loading, before headers
// wp_enqueue_scripts — Enqueue frontend CSS/JS
// admin_menu — Register admin menu pages
// save_post — A post was saved (for custom processing)
// rest_api_init — Register custom REST API endpoints
// wp_head — Inject into <head>
// wp_footer — Inject before </body>// FILTERS — "Modify a value and return it"
// They MUST return the modified (or original) value.
// Modify the page title
add_filter('the_title', function($title) {
if (is_single()) {
return '📄 ' . $title;
}
return $title;
});
// Modify the main query
add_filter('pre_get_posts', function($query) {
if (!is_admin() && $query->is_main_query() && is_home()) {
$query->set('posts_per_page', 12);
$query->set('post_type', ['post', 'product']);
}
return $query;
});
// Common filters to know:
// the_content — Modify post content before display
// the_title — Modify post title
// excerpt_length — Change excerpt word count
// upload_mimes — Allow/disallow file upload types
// body_class — Add CSS classes to <body>
// wp_nav_menu_items — Modify navigation menu itemsHook Priority & Execution Order
// Third parameter is priority (default 10). Lower = runs first.
add_action('init', 'runs_second', 10);
add_action('init', 'runs_first', 5);
add_action('init', 'runs_third', 20);
// Fourth parameter is number of accepted arguments
add_filter('the_content', 'add_author_box', 10, 1);
add_action('save_post', 'sync_to_crm', 10, 3); // receives $post_id, $post, $updateThe Loop & WP_Query
The Loop is how WordPress displays content. WP_Query is the class that powers it.
// The standard Loop (in template files)
if (have_posts()) :
while (have_posts()) : the_post();
the_title('<h2>', '</h2>');
the_content();
the_post_thumbnail('large');
endwhile;
the_posts_pagination();
else :
echo '<p>No posts found.</p>';
endif;
// Custom WP_Query (for custom sections on a page)
$featured = new WP_Query([
'post_type' => 'product',
'posts_per_page' => 6,
'meta_key' => 'is_featured',
'meta_value' => '1',
'orderby' => 'date',
'order' => 'DESC',
'tax_query' => [
[
'taxonomy' => 'product_category',
'field' => 'slug',
'terms' => 'electronics',
],
],
]);
if ($featured->have_posts()) :
while ($featured->have_posts()) : $featured->the_post();
// Display each product
endwhile;
wp_reset_postdata(); // CRITICAL: always reset after custom query
endif;
// get_posts() — simpler alternative for quick queries
$recent = get_posts([
'post_type' => 'post',
'numberposts' => 5,
'orderby' => 'date',
]);Plugin Development
<?php
/**
* Plugin Name: My Custom Plugin
* Description: A brief description of what it does.
* Version: 1.0.0
* Author: Arvind
* Text Domain: my-custom-plugin
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Activation hook — runs once when plugin is activated
register_activation_hook(__FILE__, function() {
// Create custom database tables
global $wpdb;
$table = $wpdb->prefix . 'custom_logs';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
action VARCHAR(100) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id)
) $charset;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
// Flush rewrite rules if registering custom post types
flush_rewrite_rules();
});
// Deactivation hook
register_deactivation_hook(__FILE__, function() {
flush_rewrite_rules();
});
// Use OOP structure for larger plugins
class MyCustomPlugin {
private static ?self $instance = null;
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_action('init', [$this, 'registerPostTypes']);
add_action('rest_api_init', [$this, 'registerApiRoutes']);
add_action('admin_menu', [$this, 'registerAdminPages']);
}
public function registerPostTypes(): void { /* ... */ }
public function registerApiRoutes(): void { /* ... */ }
public function registerAdminPages(): void { /* ... */ }
}
MyCustomPlugin::getInstance();WordPress REST API
The REST API (since 4.7) makes WordPress a headless CMS. Built-in endpoints at /wp-json/wp/v2/.
// Register a custom REST endpoint
add_action('rest_api_init', function() {
register_rest_route('myplugin/v1', '/products', [
'methods' => 'GET',
'callback' => 'get_products',
'permission_callback' => '__return_true', // Public endpoint
'args' => [
'category' => [
'required' => false,
'validate_callback' => fn($param) => is_string($param),
'sanitize_callback' => 'sanitize_text_field',
],
],
]);
register_rest_route('myplugin/v1', '/products', [
'methods' => 'POST',
'callback' => 'create_product',
'permission_callback' => function() {
return current_user_can('edit_posts'); // Auth check
},
]);
});
function get_products(WP_REST_Request $request): WP_REST_Response {
$category = $request->get_param('category');
$query = new WP_Query([
'post_type' => 'product',
'posts_per_page' => 20,
]);
$products = array_map(function($post) {
return [
'id' => $post->ID,
'title' => $post->post_title,
'price' => get_post_meta($post->ID, 'price', true),
];
}, $query->posts);
return new WP_REST_Response($products, 200);
}
// Built-in endpoints you should know:
// GET /wp-json/wp/v2/posts — List posts
// GET /wp-json/wp/v2/posts/{id} — Single post
// POST /wp-json/wp/v2/posts — Create post (auth required)
// PUT /wp-json/wp/v2/posts/{id} — Update post
// DELETE /wp-json/wp/v2/posts/{id} — Delete post
// GET /wp-json/wp/v2/users/me — Current user infoGutenberg & Block Development
Gutenberg is WordPress's block-based editor, built with React. Block development is now a key WordPress skill.
// block.json — Block metadata (required since WP 5.8)
{
"apiVersion": 3,
"name": "myplugin/hero-banner",
"title": "Hero Banner",
"category": "design",
"icon": "cover-image",
"description": "A customizable hero banner block.",
"supports": {
"html": false,
"align": ["wide", "full"],
"color": { "background": true, "text": true }
},
"attributes": {
"heading": { "type": "string", "default": "" },
"imageUrl": { "type": "string", "default": "" }
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
// Register in PHP
add_action('init', function() {
register_block_type(__DIR__ . '/blocks/hero-banner');
});
// edit.js (React component for the editor)
import { useBlockProps, RichText, MediaUpload } from '@wordpress/block-editor';
export default function Edit({ attributes, setAttributes }) {
const blockProps = useBlockProps();
return (
<div {...blockProps}>
<RichText
tagName="h1"
value={attributes.heading}
onChange={(heading) => setAttributes({ heading })}
placeholder="Enter heading..."
/>
</div>
);
}Security Hardening
WordPress security is a favourite interview topic because of the platform's massive attack surface.
- Data Sanitization: Use
sanitize_text_field(),sanitize_email(),wp_kses_post()on all user inputs. - Data Escaping: Use
esc_html(),esc_attr(),esc_url(),wp_kses()on all outputs. - Nonce Verification: All form submissions and AJAX calls must include a nonce (
wp_nonce_field(),wp_verify_nonce()). - Capability Checks: Always check
current_user_can('manage_options')before privileged operations. - Database Queries: Use
$wpdb->prepare()for all custom queries. Never interpolate user input. - File Permissions: wp-config.php should be 400/440. Disable file editing in the admin:
define('DISALLOW_FILE_EDIT', true).
Performance Optimization
- Object Caching: Use Redis or Memcached as a persistent object cache (via
wp_cache_set/get). Without this, WordPress queries the DB on every page load. - Transients API: Cache expensive queries:
set_transient('key', $data, HOUR_IN_SECONDS). Stored in the database (or object cache if available). - Full-Page Caching: Plugins like WP Super Cache or server-level (Nginx FastCGI cache, Varnish) bypass PHP entirely for cached pages.
- Autoloaded Options: Options with
autoload=yesare loaded into memory on every page load. Audit large autoloaded options (serialized arrays in wp_options). - Reduce Plugins: Each plugin fires hooks and loads files. 50+ plugins = slow site. Audit with Query Monitor plugin.
- Image Optimization: Use WebP/AVIF formats, lazy loading (
loading="lazy"is default since WP 5.5), and proper srcset for responsive images.
Headless WordPress
Using WordPress as a backend CMS with a decoupled frontend (Next.js, Nuxt, Gatsby).
- Architecture: WordPress serves content via REST API or WPGraphQL plugin. A separate frontend app fetches and renders it.
- Pros: Modern frontend stack, better performance (SSG/ISR), improved security (no PHP frontend exposed), flexible multi-platform delivery.
- Cons: No live preview (without extra work), no visual editor for non-technical users, plugin frontend features break, increased complexity.
- WPGraphQL: A popular plugin that exposes WordPress data via a GraphQL API, more efficient than REST for complex frontends.
WordPress Multisite
A single WordPress installation managing multiple sites. Common in enterprises and SaaS platforms.
- Use Cases: University departments, franchise websites, white-label SaaS products.
- Subdomain vs Subdirectory:
site1.example.comvsexample.com/site1. - Super Admin: Has access to all sites and the Network Admin dashboard.
- Shared Resources: Plugins and themes are installed once and activated per-site.
Interview Quick Reference
| Topic | Key Points to Mention |
|---|---|
| Hooks | Actions (side effects) vs Filters (modify values). Priority system. add_action/add_filter. |
| Template Hierarchy | Most specific to least specific. single-{post_type}.php → single.php → index.php. |
| Security | Sanitize inputs, escape outputs, nonces for forms, $wpdb→prepare for queries, capability checks. |
| REST API | register_rest_route, permission_callback, WP_REST_Response. Built-in at /wp-json/wp/v2/. |
| Performance | Object cache (Redis), transients, full-page cache, autoload audit, Query Monitor for profiling. |
| Gutenberg | Block-based editor built with React. block.json, @wordpress/block-editor, Server-side rendering. |