Introduction
WordPress is powerful out of the box, but its real strength lies in extensibility. Plugins let you add features without touching WordPress core files. This is crucial – when WordPress updates, your customizations remain intact.
There are thousands of plugins available, but sometimes you need something specific. Maybe a custom widget, a specialized shortcode, or integration with your company's API. Learning to build plugins opens up endless possibilities.
Creating a WordPress plugin isn't as hard as you might think. If you know PHP basics, you can build functional plugins. Let's dive in.
What is a Plugin?
A WordPress plugin is PHP code that hooks into WordPress to add or modify functionality. Plugins can:
- Add new features (contact forms, galleries, etc.)
- Modify existing behavior (change excerpt length, add metadata)
- Integrate with external services (Twitter, payment gateways)
- Add admin interfaces (settings pages, custom post types)
- Modify appearance (add widgets, shortcodes)
Plugins live in wp-content/plugins/ and can be activated/deactivated from the WordPress admin panel.
The Simplest Plugin
A WordPress plugin needs at minimum:
- A PHP file
- A header comment
Create wp-content/plugins/hello-world.php:
<?php
/*
Plugin Name: Hello World
Plugin URI: http://example.com/
Description: A simple example plugin
Version: 1.0
Author: Your Name
Author URI: http://example.com/
License: GPL2
*/
function hello_world() {
echo '<p>Hello World from my plugin!</p>';
}
add_action('wp_footer', 'hello_world');
?>
That's it! A working plugin. Activate it in WordPress admin, and "Hello World" appears in the footer of every page.
Understanding Hooks
WordPress uses hooks to let plugins modify behavior. There are two types:
Actions: Execute code at specific points Filters: Modify data before it's displayed or saved
Hooks are the foundation of WordPress plugin development.
Using Actions
Actions let you run code when something happens:
// Add code to site footer
add_action('wp_footer', 'my_function');
// Run code when WordPress initializes
add_action('init', 'my_init_function');
// Run code when a post is published
add_action('publish_post', 'my_publish_function');
// Add to admin menu
add_action('admin_menu', 'my_menu_function');
Common action hooks:
wp_head– Inside<head>tagwp_footer– Before closing</body>init– WordPress initializationadmin_menu– Admin menu loadedsave_post– Post savedwp_enqueue_scripts– Load scripts/styles
Example – Adding Google Analytics:
function add_google_analytics() {
?>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
</script>
<?php
}
add_action('wp_footer', 'add_google_analytics');
Using Filters
Filters modify data:
// Modify post content
add_filter('the_content', 'my_content_filter');
// Change excerpt length
add_filter('excerpt_length', 'my_excerpt_length');
// Modify post title
add_filter('the_title', 'my_title_filter');
Example – Add text after post content:
function add_signature($content) {
$signature = '<p><em>Thanks for reading!</em></p>';
return $content . $signature;
}
add_filter('the_content', 'add_signature');
Example – Change excerpt length:
function custom_excerpt_length($length) {
return 50; // 50 words instead of default 55
}
add_filter('excerpt_length', 'custom_excerpt_length');
The filter receives data, modifies it, and returns it.
Plugin File Structure
Simple plugins can be a single file. Complex plugins should be organized:
my-plugin/
├── my-plugin.php # Main plugin file
├── readme.txt # Plugin documentation
├── includes/
│ ├── functions.php # Helper functions
│ └── class-widget.php # Widget class
├── admin/
│ ├── settings.php # Admin interface
│ └── admin.css # Admin styles
└── public/
├── public.js # Frontend JavaScript
└── public.css # Frontend styles
The main plugin file includes others:
<?php
/*
Plugin Name: My Plugin
*/
// Include files
require_once plugin_dir_path(__FILE__) . 'includes/functions.php';
require_once plugin_dir_path(__FILE__) . 'admin/settings.php';
// Rest of plugin code
?>
Creating a Settings Page
Most plugins need settings. Here's how to add an admin page:
<?php
/*
Plugin Name: My Settings Plugin
*/
// Add menu item
function my_plugin_menu() {
add_options_page(
'My Plugin Settings', // Page title
'My Plugin', // Menu title
'manage_options', // Capability required
'my-plugin', // Menu slug
'my_plugin_settings_page' // Function to display page
);
}
add_action('admin_menu', 'my_plugin_menu');
// Display settings page
function my_plugin_settings_page() {
?>
<div class="wrap">
<h2>My Plugin Settings</h2>
<form method="post" action="options.php">
<?php settings_fields('my-plugin-settings'); ?>
<?php do_settings_sections('my-plugin'); ?>
<?php submit_button(); ?>
</form>
</div>
<?php
}
// Register settings
function my_plugin_settings() {
register_setting('my-plugin-settings', 'my_option');
add_settings_section(
'my_plugin_section',
'Main Settings',
'my_plugin_section_callback',
'my-plugin'
);
add_settings_field(
'my_option',
'My Option',
'my_option_callback',
'my-plugin',
'my_plugin_section'
);
}
add_action('admin_init', 'my_plugin_settings');
function my_plugin_section_callback() {
echo '<p>Enter your settings below:</p>';
}
function my_option_callback() {
$option = get_option('my_option');
echo '<input type="text" name="my_option" value="' . esc_attr($option) . '" />';
}
?>
This creates a settings page under Settings > My Plugin.
Adding Shortcodes
Shortcodes let users add plugin features to posts:
// Create shortcode: [hello_message name="John"]
function hello_shortcode($atts) {
$atts = shortcode_atts(array(
'name' => 'World'
), $atts);
return '<p>Hello, ' . esc_html($atts['name']) . '!</p>';
}
add_shortcode('hello_message', 'hello_shortcode');
Use in posts: [hello_message name="John"]
Output: Hello, John!
More complex example – Recent posts:
function recent_posts_shortcode($atts) {
$atts = shortcode_atts(array(
'count' => 5
), $atts);
$posts = get_posts(array(
'numberposts' => $atts['count']
));
$output = '<ul class="recent-posts">';
foreach ($posts as $post) {
$output .= '<li><a href="' . get_permalink($post->ID) . '">'
. get_the_title($post->ID) . '</a></li>';
}
$output .= '</ul>';
return $output;
}
add_shortcode('recent_posts', 'recent_posts_shortcode');
Use: [recent_posts count="10"]
Creating Widgets
Widgets appear in sidebars. Here's a simple widget:
class My_Widget extends WP_Widget {
function __construct() {
parent::__construct(
'my_widget',
__('My Widget'),
array('description' => __('A simple widget'))
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
echo $args['before_title'] . 'My Widget' . $args['after_title'];
echo '<p>Widget content here</p>';
echo $args['after_widget'];
}
public function form($instance) {
// Widget settings form (in admin)
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">Title:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>" type="text"
value="<?php echo esc_attr($instance['title']); ?>" />
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = strip_tags($new_instance['title']);
return $instance;
}
}
// Register widget
function register_my_widget() {
register_widget('My_Widget');
}
add_action('widgets_init', 'register_my_widget');
Enqueuing Scripts and Styles
Load JavaScript and CSS properly:
function my_plugin_scripts() {
// Load on frontend only
if (!is_admin()) {
wp_enqueue_script(
'my-plugin-js',
plugins_url('/js/script.js', __FILE__),
array('jquery'),
'1.0',
true
);
wp_enqueue_style(
'my-plugin-css',
plugins_url('/css/style.css', __FILE__)
);
}
}
add_action('wp_enqueue_scripts', 'my_plugin_scripts');
This ensures scripts load in the right order and don't conflict.
Database Operations
Plugins can store data in the database:
// Save data
update_option('my_plugin_data', $data);
// Get data
$data = get_option('my_plugin_data');
// Delete data
delete_option('my_plugin_data');
For plugin-specific tables:
function my_plugin_install() {
global $wpdb;
$table_name = $wpdb->prefix . 'my_plugin_data';
$sql = "CREATE TABLE $table_name (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (id)
)";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'my_plugin_install');
This creates a table when the plugin activates.
Activation and Deactivation Hooks
Run code when plugin is activated/deactivated:
// Activation
function my_plugin_activate() {
// Create database tables
// Set default options
// Schedule cron jobs
}
register_activation_hook(__FILE__, 'my_plugin_activate');
// Deactivation
function my_plugin_deactivate() {
// Clear scheduled cron jobs
// Don't delete data (user might reactivate)
}
register_deactivation_hook(__FILE__, 'my_plugin_deactivate');
// Uninstall (complete removal)
function my_plugin_uninstall() {
// Delete options
// Drop database tables
// Clean up completely
}
register_uninstall_hook(__FILE__, 'my_plugin_uninstall');
Security Best Practices
Sanitize input:
$value = sanitize_text_field($_POST['field']);
Escape output:
echo esc_html($user_input);
echo esc_url($url);
echo esc_attr($attribute);
Use nonces for forms:
// Generate nonce
wp_nonce_field('my_action', 'my_nonce');
// Verify nonce
if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
die('Security check failed');
}
Check capabilities:
if (!current_user_can('manage_options')) {
wp_die('Insufficient permissions');
}
Never trust user input. Always sanitize and validate.
Debugging Plugins
Enable debugging in wp-config.php:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Use error_log():
error_log('Debug message: ' . print_r($variable, true));
Check wp-content/debug.log for errors.
Plugin Development Tips
Use meaningful function names: Prefix functions with your plugin name to avoid conflicts:
// Bad
function get_data() { }
// Good
function myplugin_get_data() { }
Check if function exists:
if (!function_exists('myplugin_function')) {
function myplugin_function() {
// Code here
}
}
Use WordPress functions: Don't reinvent the wheel. WordPress has functions for everything.
Comment your code: Future you will thank present you.
Test on fresh WordPress installs: Don't assume other plugins are present.
Practical Example: View Counter Plugin
Let's build a complete post view counter:
<?php
/*
Plugin Name: Simple View Counter
Description: Counts post views
Version: 1.0
Author: Your Name
*/
// Count views
function svc_count_views() {
if (is_single()) {
global $post;
$count = get_post_meta($post->ID, 'view_count', true);
$count = $count ? $count : 0;
$count++;
update_post_meta($post->ID, 'view_count', $count);
}
}
add_action('wp_head', 'svc_count_views');
// Display view count
function svc_display_views($post_id = null) {
if (!$post_id) {
global $post;
$post_id = $post->ID;
}
$count = get_post_meta($post_id, 'view_count', true);
$count = $count ? $count : 0;
return $count . ' views';
}
// Shortcode
function svc_shortcode() {
return svc_display_views();
}
add_shortcode('view_count', 'svc_shortcode');
// Add to post content
function svc_add_to_content($content) {
if (is_single()) {
$views = svc_display_views();
$content .= '<p><em>' . $views . '</em></p>';
}
return $content;
}
add_filter('the_content', 'svc_add_to_content');
?>
This plugin:
- Counts views when someone visits a post
- Stores count in post meta
- Displays count after post content
- Provides
[view_count]shortcode
Distribution and Updates
Create readme.txt: WordPress.org plugins require a readme.txt file with specific format. Include:
- Plugin description
- Installation instructions
- Changelog
- FAQ
Version control: Use SVN for WordPress.org or Git for private repos.
Semantic versioning: Use version numbers like 1.0.0, 1.1.0, 2.0.0.
Finding More Hooks
WordPress has hundreds of hooks. Find them:
WordPress Codex: http://codex.wordpress.org/Plugin_API
Action Reference: http://codex.wordpress.org/Plugin_API/Action_Reference
Filter Reference: http://codex.wordpress.org/Plugin_API/Filter_Reference
Search WordPress core: Look at core files to see what hooks are available.
Resources for Learning
WordPress Codex: Official documentation
WordPress StackExchange: Q&A for WordPress developers
Existing plugins: Read code from popular plugins on WordPress.org
WordPress source code: The best documentation is the code itself
Final Thoughts
WordPress plugin development opens up endless possibilities. Start small – a simple shortcode or widget. As you learn, build more complex plugins.
The WordPress hooks system is powerful. Learn the common hooks, understand actions vs filters, and you'll be able to extend WordPress in any way you need.
Don't be intimidated. Every plugin developer started where you are. Build something simple, test it, improve it. Before long, you'll be building sophisticated plugins.
The WordPress community is helpful. When you get stuck, search the forums, ask on StackExchange, or read plugin source code. Someone has probably solved your problem before.
Start building. Your first plugin is waiting to be written.