A WordPress plugin consists of several essential files:
<?php
/**
* Plugin Name: My Custom Plugin
* Plugin URI: https://example.com/my-plugin
* Description: A custom WordPress plugin for professional websites
* Version: 1.0.0
* 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
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// 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__));
// Main plugin class
class MyPlugin {
public function __construct() {
add_action('init', array($this, 'init'));
add_action('admin_menu', array($this, 'admin_menu'));
}
public function init() {
// Initialize plugin
load_plugin_textdomain('my-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
public function admin_menu() {
add_menu_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
array($this, 'admin_page'),
'dashicons-admin-generic'
);
}
public function admin_page() {
include MY_PLUGIN_PATH . 'admin/admin-page.php';
}
}
// Initialize plugin
new MyPlugin();// Activation hook
register_activation_hook(__FILE__, 'my_plugin_activate');
function my_plugin_activate() {
// Create database tables
global $wpdb;
$table_name = $wpdb->prefix . 'my_plugin_data';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// Set default options
add_option('my_plugin_version', MY_PLUGIN_VERSION);
}
// Deactivation hook
register_deactivation_hook(__FILE__, 'my_plugin_deactivate');
function my_plugin_deactivate() {
// Clean up scheduled events
wp_clear_scheduled_hook('my_plugin_cron');
}
// Uninstall hook (in uninstall.php)
function my_plugin_uninstall() {
// Remove database tables
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}my_plugin_data");
// Remove options
delete_option('my_plugin_version');
}// Create nonce
$nonce = wp_create_nonce('my_plugin_action');
// Verify nonce
if (wp_verify_nonce($_POST['nonce'], 'my_plugin_action')) {
// Action is secure
} else {
wp_die('Security check failed');
}// Escape output
echo esc_html($user_input);
echo esc_attr($user_input);
echo esc_url($user_input);
echo wp_kses_post($user_input); // Allow safe HTML// Sanitize input
$clean_input = sanitize_text_field($_POST['user_input']);
$clean_email = sanitize_email($_POST['email']);
$clean_url = esc_url_raw($_POST['url']);WordPress uses a hook system that allows plugins to "hook into" specific points in the execution flow:
// Hook into WordPress actions
add_action('wp_head', 'my_plugin_head_function');
add_action('wp_footer', 'my_plugin_footer_function');
add_action('save_post', 'my_plugin_save_post_function', 10, 2);// Modify WordPress data
add_filter('the_content', 'my_plugin_content_filter');
add_filter('the_title', 'my_plugin_title_filter');
add_filter('wp_mail_content_type', 'my_plugin_mail_filter');// Define custom action
do_action('my_plugin_custom_action', $param1, $param2);
// Hook into custom action
add_action('my_plugin_custom_action', 'my_custom_function', 10, 2);
// Define custom filter
$result = apply_filters('my_plugin_custom_filter', $value, $param1);
// Hook into custom filter
add_filter('my_plugin_custom_filter', 'my_custom_filter_function', 10, 2);function my_plugin_register_post_types() {
register_post_type('portfolio', array(
'labels' => array(
'name' => 'Portfolio',
'singular_name' => 'Portfolio Item',
'add_new' => 'Add New',
'add_new_item' => 'Add New Portfolio Item',
'edit_item' => 'Edit Portfolio Item',
'new_item' => 'New Portfolio Item',
'view_item' => 'View Portfolio Item',
'search_items' => 'Search Portfolio',
'not_found' => 'No portfolio items found',
'not_found_in_trash' => 'No portfolio items found in trash'
),
'public' => true,
'has_archive' => true,
'supports' => array('title', 'editor', 'thumbnail', 'excerpt'),
'menu_icon' => 'dashicons-portfolio',
'rewrite' => array('slug' => 'portfolio'),
'show_in_rest' => true, // Enable Gutenberg editor
));
}
add_action('init', 'my_plugin_register_post_types');function my_plugin_register_taxonomies() {
register_taxonomy('project_type', 'portfolio', array(
'labels' => array(
'name' => 'Project Types',
'singular_name' => 'Project Type',
'search_items' => 'Search Project Types',
'all_items' => 'All Project Types',
'parent_item' => 'Parent Project Type',
'parent_item_colon' => 'Parent Project Type:',
'edit_item' => 'Edit Project Type',
'update_item' => 'Update Project Type',
'add_new_item' => 'Add New Project Type',
'new_item_name' => 'New Project Type Name',
'menu_name' => 'Project Types'
),
'hierarchical' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'project-type'),
'show_in_rest' => true,
));
}
add_action('init', 'my_plugin_register_taxonomies');// Query custom post types
$portfolio_query = new WP_Query(array(
'post_type' => 'portfolio',
'posts_per_page' => 6,
'tax_query' => array(
array(
'taxonomy' => 'project_type',
'field' => 'slug',
'terms' => 'web-design',
),
),
'meta_query' => array(
array(
'key' => 'featured_project',
'value' => '1',
'compare' => '='
)
)
));
if ($portfolio_query->have_posts()) {
while ($portfolio_query->have_posts()) {
$portfolio_query->the_post();
// Display portfolio item
}
wp_reset_postdata();
}global $wpdb;
// Prepare statements to prevent SQL injection
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}my_plugin_data WHERE status = %s AND user_id = %d",
'active',
get_current_user_id()
)
);
// Insert data safely
$wpdb->insert(
$wpdb->prefix . 'my_plugin_data',
array(
'title' => sanitize_text_field($_POST['title']),
'content' => sanitize_textarea_field($_POST['content']),
'user_id' => get_current_user_id()
),
array('%s', '%s', '%d')
);
// Update data safely
$wpdb->update(
$wpdb->prefix . 'my_plugin_data',
array('status' => 'inactive'),
array('id' => $post_id),
array('%s'),
array('%d')
);// Store data permanently
add_option('my_plugin_setting', 'value');
update_option('my_plugin_setting', 'new_value');
$value = get_option('my_plugin_setting', 'default_value');
// Store arrays
update_option('my_plugin_settings', array(
'setting1' => 'value1',
'setting2' => 'value2'
));// Store data temporarily (with expiration)
set_transient('my_plugin_cache', $data, HOUR_IN_SECONDS);
$cached_data = get_transient('my_plugin_cache');
// Delete transients
delete_transient('my_plugin_cache');function my_plugin_create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$wpdb->prefix}my_plugin_orders (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
order_number varchar(50) NOT NULL,
total decimal(10,2) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
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 order_number (order_number),
KEY status (status)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}function my_plugin_register_rest_routes() {
register_rest_route('my-plugin/v1', '/data', array(
'methods' => 'GET',
'callback' => 'my_plugin_get_data',
'permission_callback' => 'my_plugin_check_permissions',
));
register_rest_route('my-plugin/v1', '/data', array(
'methods' => 'POST',
'callback' => 'my_plugin_create_data',
'permission_callback' => 'my_plugin_check_permissions',
));
}
add_action('rest_api_init', 'my_plugin_register_rest_routes');
function my_plugin_get_data($request) {
global $wpdb;
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}my_plugin_data");
return new WP_REST_Response($results, 200);
}
function my_plugin_create_data($request) {
$params = $request->get_params();
global $wpdb;
$result = $wpdb->insert(
$wpdb->prefix . 'my_plugin_data',
array(
'title' => sanitize_text_field($params['title']),
'content' => sanitize_textarea_field($params['content'])
)
);
if ($result) {
return new WP_REST_Response(array('success' => true), 201);
} else {
return new WP_Error('insert_failed', 'Failed to insert data', array('status' => 500));
}
}
function my_plugin_check_permissions() {
return current_user_can('manage_options');
}// Check user capabilities
function my_plugin_check_user_permissions($request) {
$user_id = get_current_user_id();
if (!$user_id) {
return false;
}
$user = get_userdata($user_id);
// Check specific capabilities
if (!current_user_can('edit_posts')) {
return false;
}
return true;
}
// Add custom authentication
function my_plugin_authenticate_request($request) {
$auth_header = $request->get_header('Authorization');
if (!$auth_header) {
return false;
}
// Validate API key or token
$api_key = str_replace('Bearer ', '', $auth_header);
if ($api_key !== get_option('my_plugin_api_key')) {
return false;
}
return true;
}// Disable frontend for headless setup
function my_plugin_headless_redirect() {
if (!is_admin() && !wp_doing_ajax() && !wp_doing_cron()) {
wp_redirect('https://your-frontend-app.com');
exit;
}
}
add_action('template_redirect', 'my_plugin_headless_redirect');
// Add CORS headers for frontend
function my_plugin_add_cors_headers() {
header('Access-Control-Allow-Origin: https://your-frontend-app.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
}
add_action('rest_api_init', 'my_plugin_add_cors_headers');You've now learned the fundamentals of WordPress plugin development! In Part 4: Advanced Developer Skills, you'll build a real-world plugin and learn about internationalization, testing, and debugging.
Resources: