This complete WordPress Plugin Development tutorial covers 95 topics — from your first plugin file to hooks, CPT, REST, AJAX, WooCommerce, security, i18n, and wordpress.org release. Beginner-friendly with production-minded code.
Course roadmap
- Introduction to WordPress Plugin Development
- What is a WordPress Plugin?
- WordPress Plugin Architecture
- Creating Your First WordPress Plugin
- WordPress Plugin File Structure
- Plugin Header Information
- Activating and Deactivating Plugins
- Plugin Activation and Deactivation Hooks
- Uninstalling a Plugin
- WordPress Actions and Filters
- Understanding WordPress Hooks
- Creating Custom Actions
- Creating Custom Filters
- WordPress Coding Standards
- Enqueue CSS and JavaScript
- Adding Custom JavaScript
- Adding Custom CSS
- WordPress Admin Menu
- Creating Admin Settings Pages
- WordPress Settings API
- WordPress Options API
- Creating Custom Admin Forms
- Nonces and Security in WordPress Plugins
- User Capabilities and Permissions
- Sanitization and Validation
- Escaping WordPress Data
- WordPress Database Structure
- The $wpdb Class
- Creating Custom Database Tables
- CRUD Operations with $wpdb
- Database Migrations and Updates
- Custom Post Types
- Custom Taxonomies
- Custom Fields in WordPress Plugins
- Meta Boxes in WordPress
- Post Meta and User Meta
- Shortcodes in WordPress
- Creating Custom Shortcodes
- Widgets in WordPress Plugins
- Gutenberg Block Development
- Creating Custom Gutenberg Blocks
- Dynamic Gutenberg Blocks
- WordPress REST API
- Creating Custom REST API Endpoints
- REST API Authentication
- AJAX in WordPress
- wp_ajax and wp_ajax_nopriv
- Form Submission with AJAX
- WordPress Forms and Validation
- File Upload Handling in Plugins
- Media Library Integration
- Cron Jobs / WP-Cron
- Sending Emails with wp_mail()
- SMTP Integration for WordPress Plugins
- User Registration and Login in Plugins
- User Roles and Capabilities
- WooCommerce Plugin Integration
- WooCommerce Hooks and Filters
- Payment Gateway Plugin Development
- Third-Party API Integration
- Webhooks Integration
- OAuth Authentication in WordPress Plugins
- API Requests with wp_remote_get()
- API Requests with wp_remote_post()
- Plugin Settings Import/Export
- Plugin Localization and Translation
- Internationalization (i18n)
- Multilingual Plugin Compatibility
- Plugin Performance Optimization
- WordPress Caching for Plugins
- Plugin Security Best Practices
- Preventing CSRF in WordPress Plugins
- Preventing SQL Injection
- Preventing XSS in WordPress Plugins
- Preventing Unauthorized Access
- WordPress Debugging for Plugin Developers
- Error Handling and Logging
- Plugin Compatibility Testing
- WordPress Version Compatibility
- PHP Version Compatibility
- Unit Testing WordPress Plugins
- Plugin Documentation
- Creating Free and Pro Plugin Versions
- Plugin Licensing System
- Plugin Update Mechanism
- Automatic Plugin Updates
- Preparing a Plugin for WordPress.org
- readme.txt and Plugin Assets
- WordPress.org Plugin Review Guidelines
- SVN and WordPress.org Plugin Deployment
- Git and Plugin Development Workflow
- Plugin Release and Version Management
- Building a Complete WordPress Plugin
- WordPress Plugin Development Interview Questions
- Final Project – Complete Professional WordPress Plugin
1. Introduction to WordPress Plugin Development
WordPress plugins extend core features without editing WordPress itself. This series covers hooks, admin UI, CPT, REST, AJAX, security, WooCommerce, and shipping to WordPress.org.
- Learn plugin structure and hooks first.
- Build admin settings and secure forms.
- Ship a complete plugin with docs and updates.
Learning path
Basics + hooks → admin/settings/security
CPT/taxonomies/meta → shortcodes/blocks
REST/AJAX → WooCommerce/APIs
Security/i18n → wordpress.org release
2. What is a WordPress Plugin?
A plugin is a PHP (and JS/CSS) package that hooks into WordPress actions/filters to add features — themes change design; plugins change functionality.
Plugin vs theme
Theme → look & layout
Plugin → features & behavior
Best practice: keep logic in plugins
3. WordPress Plugin Architecture
Typical architecture: main bootstrap file, includes for admin/public, assets, and hooks registered on load. Keep responsibilities separated.
Common layout
my-plugin/
my-plugin.php (bootstrap + header)
includes/
admin/
public/
assets/css|js
languages/
4. Creating Your First WordPress Plugin
Put a PHP file with a valid plugin header inside `wp-content/plugins/your-plugin/`, then activate it in Plugins.
- Create `wp-content/plugins/hello-plugin/`.
- Add a main PHP file with a plugin header.
- Activate it in wp-admin → Plugins.
hello-plugin.php
<?php
/**
* Plugin Name: Hello Plugin
* Description: My first WordPress plugin.
* Version: 1.0.0
* Author: Imtiyaj
*/
add_action('init', function () {
// Plugin loaded.
});
5. WordPress Plugin File Structure
Separate admin and front-end code, keep assets versioned, and autoload classes when the plugin gets larger.
Recommended structure
my-plugin/
my-plugin.php
uninstall.php
includes/class-plugin.php
admin/class-admin.php
public/class-public.php
assets/
languages/
6. Plugin Header Information
WordPress reads the header comment to list your plugin. Include license, text domain, and requires versions for clarity.
Full header example
<?php
/**
* Plugin Name: Acme Tools
* Plugin URI: https://example.com/acme-tools
* Description: Helpful tools for editors.
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: Acme
* License: GPL-2.0-or-later
* Text Domain: acme-tools
* Domain Path: /languages
*/
7. Activating and Deactivating Plugins
Activation should set defaults and capabilities carefully. Deactivation should stop running features but usually keep data unless uninstalling.
Lifecycle note
Activate → setup defaults, flush rewrites if needed
Deactivate → pause features, keep data
Uninstall → remove options/tables (only if intended)
8. Plugin Activation and Deactivation Hooks
Use activation for DB/version setup. Use deactivation for cleanup like clearing cron events.
Hook registration
register_activation_hook(__FILE__, 'acme_activate');
register_deactivation_hook(__FILE__, 'acme_deactivate');
function acme_activate() {
add_option('acme_version', '1.0.0');
flush_rewrite_rules();
}
function acme_deactivate() {
wp_clear_scheduled_hook('acme_daily_event');
flush_rewrite_rules();
}
9. Uninstalling a Plugin
Only delete data on uninstall if that is expected. Check the uninstall constant and user capability.
uninstall.php
<?php
if (! defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
delete_option('acme_settings');
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}acme_items");
10. WordPress Actions and Filters
Actions let you run code at moments in the WP lifecycle. Filters let you modify data before it is used/displayed.
Quick comparison
Action → do something (side effects)
Filter → change a value and return it
11. Understanding WordPress Hooks
Hooks are the core extension system. Priority controls order; accepted_args controls how many parameters your callback receives.
Hook basics
add_action('init', 'acme_boot', 10);
add_filter('the_title', 'acme_prefix_title', 10, 2);
function acme_prefix_title($title, $post_id) {
return '[ACME] ' . $title;
}
12. Creating Custom Actions
Use `do_action()` at extension points. Document hook names and arguments for developers.
Custom action
// In your plugin:
do_action('acme_after_save_item', $item_id, $data);
// Elsewhere:
add_action('acme_after_save_item', function ($item_id, $data) {
// react
}, 10, 2);
13. Creating Custom Filters
Always return a value from filter callbacks. Provide sensible defaults.
Custom filter
$label = apply_filters('acme_button_label', 'Save');
add_filter('acme_button_label', function ($label) {
return 'Save changes';
});
14. WordPress Coding Standards
Use WordPress naming, Yoda-friendly comparisons where required by WPCS, proper escaping, and PHPCS with WordPress rulesets.
Style tips
- Prefix functions/classes/options
- Escape output, sanitize input
- Use strict comparisons when sensible
- Keep files focused and documented
15. Enqueue CSS and JavaScript
Never hardcode script tags in most cases. Enqueue with dependencies, versioning, and correct hooks (`wp_enqueue_scripts` / `admin_enqueue_scripts`).
Enqueue assets
add_action('wp_enqueue_scripts', function () {
wp_enqueue_style('acme', plugins_url('assets/css/acme.css', __FILE__), [], '1.0.0');
wp_enqueue_script('acme', plugins_url('assets/js/acme.js', __FILE__), ['jquery'], '1.0.0', true);
});
16. Adding Custom JavaScript
Localize AJAX URLs and nonces for front-end scripts. Keep JS modular as features grow.
Localize script
wp_localize_script('acme', 'AcmeData', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('acme_ajax'),
]);
17. Adding Custom CSS
Prefix CSS classes with your plugin slug. Load admin CSS only on your plugin screens when possible.
Admin-only CSS
add_action('admin_enqueue_scripts', function ($hook) {
if ($hook !== 'toplevel_page_acme-settings') {
return;
}
wp_enqueue_style('acme-admin', plugins_url('assets/css/admin.css', __FILE__), [], '1.0.0');
});
18. WordPress Admin Menu
Use `add_menu_page` / `add_submenu_page` with capability checks so only allowed users see the screens.
Admin menu
add_action('admin_menu', function () {
add_menu_page(
'Acme Settings',
'Acme',
'manage_options',
'acme-settings',
'acme_render_settings',
'dashicons-admin-generic',
58
);
});
19. Creating Admin Settings Pages
Render forms with Settings API fields, show success notices, and keep markup accessible.
Render callback sketch
function acme_render_settings() {
if (! current_user_can('manage_options')) {
return;
}
echo '<div class="wrap"><h1>Acme Settings</h1>';
echo '<form method="post" action="options.php">';
settings_fields('acme_settings_group');
do_settings_sections('acme-settings');
submit_button();
echo '</form></div>';
}
20. WordPress Settings API
Settings API handles nonces, capabilities, and option saving patterns more safely than raw forms.
Register setting
add_action('admin_init', function () {
register_setting('acme_settings_group', 'acme_settings', [
'type' => 'array',
'sanitize_callback' => 'acme_sanitize_settings',
'default' => [],
]);
});
21. WordPress Options API
Prefer a single array option for related settings. Autoload only what is needed on every request.
Options usage
$settings = get_option('acme_settings', []);
$settings['api_key'] = 'xxx';
update_option('acme_settings', $settings, false);
22. Creating Custom Admin Forms
For non-Settings-API forms, verify nonce + capability, sanitize input, then redirect with a query arg notice.
Handle POST
if (isset($_POST['acme_save'])) {
check_admin_referer('acme_save_action');
if (! current_user_can('manage_options')) {
wp_die('Forbidden');
}
$title = sanitize_text_field(wp_unslash($_POST['title'] ?? ''));
// save...
wp_safe_redirect(add_query_arg('updated', '1'));
exit;
}
23. Nonces and Security in WordPress Plugins
Nonces help verify intent/origin for forms and AJAX. Always pair with capability checks — nonces are not permissions.
Create and verify
wp_nonce_field('acme_save_action');
// ...
check_admin_referer('acme_save_action');
// AJAX:
check_ajax_referer('acme_ajax', 'nonce');
24. User Capabilities and Permissions
Check capabilities before rendering UI and before processing actions. Map custom caps to roles on activation if needed.
Capability check
if (! current_user_can('manage_options')) {
wp_die(esc_html__('You do not have permission.', 'acme-tools'));
}
25. Sanitization and Validation
Sanitize cleans data. Validate checks business rules. Do both on input; escape on output.
Sanitize helpers
$email = sanitize_email(wp_unslash($_POST['email'] ?? ''));
$url = esc_url_raw(wp_unslash($_POST['url'] ?? ''));
$html = wp_kses_post(wp_unslash($_POST['content'] ?? ''));
if (! is_email($email)) {
// invalid
}
26. Escaping WordPress Data
Escape late (when printing). Choose the right esc_* helper for the context.
Escape examples
echo esc_html($title);
echo '<a href="' . esc_url($link) . '">' . esc_html($label) . '</a>';
echo '<input value="' . esc_attr($value) . '">';
27. WordPress Database Structure
Understanding core schema helps you decide between post meta, custom tables, or taxonomies.
Core tables (common)
wp_posts / wp_postmeta
wp_users / wp_usermeta
wp_options
wp_terms / wp_term_taxonomy / wp_term_relationships
28. The $wpdb Class
Use `$wpdb->prepare()` for dynamic SQL. Prefer WP APIs when they fit; use `$wpdb` for custom tables/queries.
Prepared query
global $wpdb;
$row = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$wpdb->prefix}acme_items WHERE id = %d", $id)
);
29. Creating Custom Database Tables
Use `dbDelta` carefully (exact formatting matters). Store a DB version option for upgrades.
dbDelta create
global $wpdb;
$table = $wpdb->prefix . 'acme_items';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) $charset;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
30. CRUD Operations with $wpdb
`insert`, `update`, `delete`, and `get_*` methods cover most custom-table CRUD needs.
CRUD helpers
global $wpdb;
$table = $wpdb->prefix . 'acme_items';
$wpdb->insert($table, ['title' => 'Hello', 'created_at' => current_time('mysql')], ['%s','%s']);
$wpdb->update($table, ['title' => 'Hi'], ['id' => 1], ['%s'], ['%d']);
$wpdb->delete($table, ['id' => 1], ['%d']);
31. Database Migrations and Updates
Compare stored DB version to plugin DB version on `plugins_loaded`/`admin_init`, then migrate and update the option.
Versioned upgrade
define('ACME_DB_VERSION', '1.1.0');
add_action('plugins_loaded', function () {
if (get_option('acme_db_version') === ACME_DB_VERSION) {
return;
}
// run migrations...
update_option('acme_db_version', ACME_DB_VERSION);
});
32. Custom Post Types
CPTs power portfolios, products, FAQs, and more. Set labels, supports, and rewrite carefully.
Register CPT
add_action('init', function () {
register_post_type('book', [
'label' => 'Books',
'public' => true,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'thumbnail'],
'has_archive' => true,
]);
});
33. Custom Taxonomies
Taxonomies can be hierarchical (like categories) or flat (like tags).
Register taxonomy
add_action('init', function () {
register_taxonomy('genre', 'book', [
'label' => 'Genres',
'hierarchical' => true,
'show_in_rest' => true,
'public' => true,
]);
});
34. Custom Fields in WordPress Plugins
Custom fields store values in postmeta. Prefer uniquely prefixed meta keys.
Save meta
update_post_meta($post_id, '_acme_isbn', sanitize_text_field($isbn));
$isbn = get_post_meta($post_id, '_acme_isbn', true);
35. Meta Boxes in WordPress
Register meta boxes, render fields, and save on `save_post` with nonce + capability checks.
Meta box sketch
add_action('add_meta_boxes', function () {
add_meta_box('acme_book', 'Book Details', 'acme_book_box', 'book');
});
add_action('save_post_book', function ($post_id) {
if (! isset($_POST['acme_book_nonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['acme_book_nonce'])), 'acme_book_save')) {
return;
}
// save fields...
});
36. Post Meta and User Meta
Use post meta for content attributes and user meta for per-user preferences/state.
User meta
update_user_meta($user_id, 'acme_timezone', 'Asia/Kolkata');
$tz = get_user_meta($user_id, 'acme_timezone', true);
37. Shortcodes in WordPress
Shortcodes are still useful for classic content and some builder workflows. Prefer blocks for new Gutenberg UIs when possible.
Shortcode idea
[acme_books limit="5"] → renders a book list
38. Creating Custom Shortcodes
Always return output (don’t echo). Escape values and sanitize attributes.
add_shortcode
add_shortcode('acme_greeting', function ($atts) {
$atts = shortcode_atts(['name' => 'friend'], $atts, 'acme_greeting');
return '<p>Hello, ' . esc_html($atts['name']) . '!</p>';
});
39. Widgets in WordPress Plugins
Widgets remain relevant for classic themes. Block themes often use block-based alternatives.
Widget sketch
class Acme_Widget extends WP_Widget {
public function __construct() {
parent::__construct('acme_widget', 'Acme Widget');
}
public function widget($args, $instance) {
echo $args['before_widget'];
echo esc_html($instance['title'] ?? 'Acme');
echo $args['after_widget'];
}
}
add_action('widgets_init', fn () => register_widget('Acme_Widget'));
40. Gutenberg Block Development
Blocks are the preferred content UI. Use `@wordpress/scripts` for a modern JS build.
Block stack
block.json + edit.js + save.js/render.php
@wordpress/scripts for bundling
register_block_type in PHP
41. Creating Custom Gutenberg Blocks
Start with a simple static block, then add attributes and controls.
Register block
add_action('init', function () {
register_block_type(__DIR__ . '/blocks/notice');
});
42. Dynamic Gutenberg Blocks
Dynamic blocks use a `render_callback` / `render.php` so output stays fresh from the database.
Render callback idea
register_block_type(__DIR__ . '/blocks/books', [
'render_callback' => function ($attributes) {
return '<div class="acme-books">...</div>';
},
]);
43. WordPress REST API
Core exposes posts, users, and more under `/wp-json/`. Plugins can add custom namespaces/routes.
Core endpoint example
GET /wp-json/wp/v2/posts
44. Creating Custom REST API Endpoints
Define namespace, route, methods, permission_callback, and args schema.
Custom route
add_action('rest_api_init', function () {
register_rest_route('acme/v1', '/items', [
'methods' => 'GET',
'callback' => 'acme_rest_items',
'permission_callback' => '__return_true',
]);
});
45. REST API Authentication
For same-origin wp-admin/editor requests, cookie + nonce works. For external clients, use application passwords or OAuth/JWT solutions.
Permission callback
'permission_callback' => function () {
return current_user_can('edit_posts');
}
46. AJAX in WordPress
WordPress AJAX uses action names hooked to `wp_ajax_{action}` and optionally `wp_ajax_nopriv_{action}`.
AJAX flow
JS → admin-ajax.php?action=acme_save
PHP hook → verify nonce/caps → JSON response
47. wp_ajax and wp_ajax_nopriv
`wp_ajax_` is for logged-in users. `wp_ajax_nopriv_` is for guests. Add both only when needed.
Register handlers
add_action('wp_ajax_acme_save', 'acme_ajax_save');
add_action('wp_ajax_nopriv_acme_save', 'acme_ajax_save');
48. Form Submission with AJAX
Send nonce + fields, verify on server, then `wp_send_json_success` / `wp_send_json_error`.
Handler sketch
function acme_ajax_save() {
check_ajax_referer('acme_ajax', 'nonce');
$title = sanitize_text_field(wp_unslash($_POST['title'] ?? ''));
if ($title === '') {
wp_send_json_error(['message' => 'Title required'], 400);
}
wp_send_json_success(['message' => 'Saved']);
}
49. WordPress Forms and Validation
Never trust client-only validation. Return clear field errors and preserve safe old input.
Validation pattern
$errors = [];
if ($email === '' || ! is_email($email)) {
$errors['email'] = 'Enter a valid email';
}
if ($errors) {
// show errors
}
50. File Upload Handling in Plugins
Check capabilities, nonces, and allowed mime types. Prefer Media Library APIs when files should be attachments.
Upload sketch
require_once ABSPATH . 'wp-admin/includes/file.php';
$file = wp_handle_upload($_FILES['acme_file'], ['test_form' => false]);
if (isset($file['error'])) {
// handle error
}
51. Media Library Integration
Use `media_handle_upload` or sideload helpers so files become attachments with metadata.
media_handle_upload
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
$attachment_id = media_handle_upload('acme_file', 0);
52. Cron Jobs / WP-Cron
WP-Cron runs on page loads by default. For critical schedules, use real server cron triggering `wp-cron.php`.
Schedule event
if (! wp_next_scheduled('acme_daily_event')) {
wp_schedule_event(time(), 'daily', 'acme_daily_event');
}
add_action('acme_daily_event', function () {
// daily task
});
53. Sending Emails with wp_mail()
`wp_mail()` is the WP wrapper around PHPMailer. Set headers for HTML/from address carefully.
wp_mail example
wp_mail(
'user@example.com',
'Welcome',
'Thanks for joining!',
['Content-Type: text/plain; charset=UTF-8']
);
54. SMTP Integration for WordPress Plugins
Hook `phpmailer_init` or integrate with an SMTP plugin/API provider. Keep credentials in protected options/env.
phpmailer_init sketch
add_action('phpmailer_init', function ($phpmailer) {
$phpmailer->isSMTP();
$phpmailer->Host = 'smtp.example.com';
$phpmailer->SMTPAuth = true;
$phpmailer->Port = 587;
$phpmailer->Username = 'user';
$phpmailer->Password = 'pass';
});
55. User Registration and Login in Plugins
Prefer core auth APIs. If building custom forms, use nonces, strong password handling, and `wp_signon` carefully.
Create user
$user_id = wp_insert_user([
'user_login' => $login,
'user_email' => $email,
'user_pass' => $password,
'role' => 'subscriber',
]);
56. User Roles and Capabilities
Use `add_role` / `get_role()->add_cap()` on activation and remove carefully on uninstall if appropriate.
Add capability
$role = get_role('editor');
if ($role) {
$role->add_cap('manage_acme_items');
}
57. WooCommerce Plugin Integration
Guard WooCommerce-dependent code. Hook into product/order lifecycle instead of editing WooCommerce core.
Dependency check
add_action('plugins_loaded', function () {
if (! class_exists('WooCommerce')) {
return;
}
// load Woo features
});
58. WooCommerce Hooks and Filters
WooCommerce is hook-rich. Prefer filters/actions over template overrides when possible.
Example filter
add_filter('woocommerce_product_get_price', function ($price, $product) {
return $price;
}, 10, 2);
59. Payment Gateway Plugin Development
Extend `WC_Payment_Gateway`, implement form fields/process_payment, and verify webhooks server-side.
Gateway checklist
1. Extend WC_Payment_Gateway
2. Register via woocommerce_payment_gateways
3. process_payment + thank you page
4. Webhook signature verification
5. Order notes + status updates
60. Third-Party API Integration
Store API keys encrypted/protected when possible, handle timeouts/errors, and never expose secrets to the browser.
Integration tips
- Keys in options/env, not JS
- Timeouts + error logging
- Retry carefully for idempotent GETs
- Cache responses when useful
61. Webhooks Integration
Expose a REST route or admin-post endpoint, verify signatures, and process quickly (queue heavy work).
Webhook route idea
register_rest_route('acme/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'acme_handle_webhook',
'permission_callback' => '__return_true',
]);
62. OAuth Authentication in WordPress Plugins
Store client IDs/secrets safely, validate state parameters, and map provider profile emails to WP users carefully.
OAuth steps
1. Redirect to provider
2. Receive code + validate state
3. Exchange code for tokens
4. Fetch profile
5. Create/login WP user
63. API Requests with wp_remote_get()
`wp_remote_get` is the WP-native way to call APIs without requiring curl extensions directly.
wp_remote_get
$response = wp_remote_get('https://api.example.com/data', [
'timeout' => 15,
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
if (is_wp_error($response)) {
return $response;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
64. API Requests with wp_remote_post()
Use JSON body + content-type headers for modern APIs. Always check for `WP_Error` and status codes.
wp_remote_post JSON
$response = wp_remote_post('https://api.example.com/items', [
'timeout' => 15,
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode(['title' => 'Hello']),
]);
65. Plugin Settings Import/Export
Validate imported JSON schema, check capabilities/nonces, and avoid executing imported PHP.
Export JSON
$settings = get_option('acme_settings', []);
header('Content-Type: application/json');
echo wp_json_encode($settings);
exit;
66. Plugin Localization and Translation
Wrap UI strings with translation functions and load `.mo` files on `init`/`plugins_loaded`.
Load text domain
add_action('init', function () {
load_plugin_textdomain('acme-tools', false, dirname(plugin_basename(__FILE__)) . '/languages');
});
echo esc_html__('Settings saved.', 'acme-tools');
67. Internationalization (i18n)
Internationalization prepares strings for translation. Localization applies a language.
i18n helpers
esc_html_e('Dashboard', 'acme-tools');
printf(
esc_html(_n('%d item', '%d items', $count, 'acme-tools')),
(int) $count
);
68. Multilingual Plugin Compatibility
Use language-aware queries where needed, translate strings via text domains, and avoid hardcoding language URLs.
Compatibility tips
- Translate all UI strings
- Store language meta when needed
- Test permalinks per language
- Avoid concatenating sentences for translation
69. Plugin Performance Optimization
Load code only when needed. Avoid unbounded queries. Profile with Query Monitor.
Perf checklist
- Conditional asset loading
- Cache remote API responses
- Avoid N+1 queries
- Autoload options carefully
- Lazy-load admin-only code
70. WordPress Caching for Plugins
Transients work everywhere; object cache shines with Redis/Memcached. Set sensible TTLs and invalidate on updates.
Transient example
$data = get_transient('acme_remote_data');
if (false === $data) {
$data = acme_fetch_remote();
set_transient('acme_remote_data', $data, HOUR_IN_SECONDS);
}
71. Plugin Security Best Practices
Security is not one function — combine authorization, authentication intent checks, input hygiene, and safe output.
Security pillars
1. Capability checks
2. Nonces for state changes
3. Sanitize/validate input
4. Escape output
5. Prepared SQL
6. Least privilege
72. Preventing CSRF in WordPress Plugins
CSRF tricks a logged-in browser into submitting requests. Nonces + same-site practices reduce this risk.
CSRF protection
check_admin_referer('acme_update');
// or for GET actions:
wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'acme_delete');
73. Preventing SQL Injection
Never concatenate unsanitized user input into SQL. Prepare values; allowlist column/orderby names.
Safe query
$rows = $wpdb->get_results(
$wpdb->prepare("SELECT * FROM {$wpdb->prefix}acme_items WHERE title LIKE %s", '%' . $wpdb->esc_like($q) . '%')
);
74. Preventing XSS in WordPress Plugins
XSS injects hostile scripts into pages. Escape by context and use `wp_kses` when HTML is required.
Safe HTML output
echo wp_kses_post($html);
echo esc_html($text);
echo esc_js($js_string);
75. Preventing Unauthorized Access
Check capabilities everywhere it matters. Guard PHP files with `ABSPATH` checks.
Direct access guard
<?php
if (! defined('ABSPATH')) {
exit;
}
76. WordPress Debugging for Plugin Developers
Use `WP_DEBUG`, `WP_DEBUG_LOG`, and Query Monitor. Fix notices before release.
wp-config debug
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
77. Error Handling and Logging
Catch expected failures, log context for yourself, and show safe messages to users.
Log error
if (is_wp_error($response)) {
error_log('Acme API failed: ' . $response->get_error_message());
}
78. Plugin Compatibility Testing
Compatibility issues often come from CSS conflicts, jQuery assumptions, or aggressive global hooks.
Test matrix idea
WP latest + previous
PHP supported versions
WooCommerce on/off
Block theme + classic theme
Caching plugin enabled
79. WordPress Version Compatibility
Use “Requires at least” accurately. Avoid relying on functions that do not exist in your minimum version without guards.
Function exists guard
if (function_exists('wp_get_theme')) {
// safe to call
}
80. PHP Version Compatibility
Declare `Requires PHP` in the header. Test on the oldest PHP you claim to support.
Header field
* Requires PHP: 7.4
81. Unit Testing WordPress Plugins
Tests protect against regressions in sanitize logic, REST permissions, and helpers.
Test mindset
Unit test pure helpers
Integration test CPT registration / REST routes
Run in CI on push
82. Plugin Documentation
Good docs reduce support load. Include screenshots, requirements, and changelog entries.
Docs checklist
README / help tab
Hook reference for developers
Changelog
Support/contact path
83. Creating Free and Pro Plugin Versions
Common patterns: free core plugin + pro add-on, or feature flags unlocked by license. Keep free valuable.
Architecture options
A) Free plugin + Pro extension plugin
B) One plugin with license-gated features
Prefer clear UX and fair free tier
84. Plugin Licensing System
Verify licenses server-side, cache status with expiry, and fail gracefully when offline.
License check idea
$response = wp_remote_post('https://example.com/license/activate', [
'body' => ['license' => $key, 'site' => home_url()],
]);
85. Plugin Update Mechanism
WordPress.org handles updates for hosted plugins. Premium plugins use custom update APIs via transient filters.
Update sources
wordpress.org SVN hosting
or
custom updater (EDD, Freemius, self-hosted API)
86. Automatic Plugin Updates
Follow semver. Use automatic updates responsibly; major breaks should be rare and documented.
Auto-update filter tip
add_filter('auto_update_plugin', function ($update, $item) {
if (isset($item->slug) && $item->slug === 'acme-tools') {
return true;
}
return $update;
}, 10, 2);
87. Preparing a Plugin for WordPress.org
Follow review guidelines, remove tracking violations, and ensure GPL-compatible licensing.
Prep checklist
- GPL-compatible license
- No obfuscated code
- Proper prefixes
- readme.txt valid
- Security basics in place
88. readme.txt and Plugin Assets
readme.txt powers the wordpress.org plugin page. Assets go in the `assets/` directory in SVN (not the plugin zip root necessarily).
readme.txt sections
Stable tag
Requires at least / Tested up to / Requires PHP
Description
Installation
FAQ
Changelog
Upgrade Notice
89. WordPress.org Plugin Review Guidelines
Avoid phone-home abuse, loading remote PHP, and trademark issues. Keep permissions honest.
Common rejection causes
- Calling home without disclosure
- Including unused/bundled libraries poorly
- Security issues (CSRF/XSS/SQLi)
- Misleading naming/branding
90. SVN and WordPress.org Plugin Deployment
Develop in Git if you want, then deploy to wordpress.org SVN (`trunk` + `tags/x.y.z`).
SVN release flow
1. svn co plugin repo
2. update trunk
3. svn cp trunk tags/1.0.1
4. svn ci -m "Release 1.0.1"
91. Git and Plugin Development Workflow
Keep main releasable. Use feature branches, semantic commits, and CI checks before merge.
Git workflow
main ← release
feature/* ← PRs
tag v1.2.0 ← releases
CI: phpcs + tests
92. Plugin Release and Version Management
Bump version in plugin header, readme stable tag, and package metadata together.
Release checklist
1. Bump version
2. Update changelog
3. Test upgrade path
4. Tag release
5. Deploy (SVN/custom updater)
93. Building a Complete WordPress Plugin
Plan features, build MVP hooks/CPT/settings, then harden security, i18n, and docs before release.
Build order
1. Bootstrap + settings
2. CPT/UI or core feature
3. Security pass
4. i18n + readme
5. QA matrix
6. Release
94. WordPress Plugin Development Interview Questions
Be ready to explain actions vs filters, nonces vs capabilities, and when to use custom tables.
Sample Q&A
Q: Action vs filter?
A: Actions run events; filters modify values.
Q: Are nonces permissions?
A: No — still check capabilities.
Q: Escape where?
A: On output, by context.
Q: When custom table?
A: High-volume/relational data unfit for postmeta.
95. Final Project – Complete Professional WordPress Plugin
Build one complete plugin end-to-end — activation, admin settings, a main feature (CPT or custom table), REST or AJAX, security hardening, translations, and release-ready readme.
- Define the feature MVP.
- Implement secure admin + data layer.
- Add REST/AJAX integration.
- Finish i18n, docs, and release checklist.
Final project scope
1. Plugin bootstrap + activation/uninstall
2. Settings API page
3. CPT + meta box OR custom table CRUD
4. Shortcode or Gutenberg block
5. REST endpoint and/or AJAX form
6. Nonces, caps, sanitize/escape
7. i18n text domain
8. readme.txt + changelog
9. Basic PHPUnit or manual test checklist
Starter bootstrap
<?php
/**
* Plugin Name: Acme Library
* Version: 1.0.0
* Text Domain: acme-library
*/
if (! defined('ABSPATH')) {
exit;
}
define('ACME_LIBRARY_VERSION', '1.0.0');
require_once __DIR__ . '/includes/class-acme-library.php';
AcmeLibrary::instance(__FILE__);
Conclusion
You now have a professional WordPress plugin path: architecture, hooks, admin settings, data storage, blocks/REST/AJAX, WooCommerce extensions, security, translations, and release workflows. Finish the final project to turn the lessons into a shippable plugin.