Categories
PHP WordPress

WordPress Plugin Development: Getting Started

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:

  1. A PHP file
  2. 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> tag
  • wp_footer – Before closing </body>
  • init – WordPress initialization
  • admin_menu – Admin menu loaded
  • save_post – Post saved
  • wp_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.

By Shishir Sharma

Shishir Sharma is a Software Engineering Leader, husband, and father based in Ottawa, Canada. A hacker and biker at heart, and has built a career as a visionary mentor and relentless problem solver.

With a leadership pedigree that includes LinkedIn, Shopify, and Zoom, Shishir excels at scaling high-impact teams and systems. He possesses a native-level mastery of JavaScript, Ruby, Python, PHP, and C/C++, moving seamlessly between modern web stacks and low-level architecture.

A dedicated member of the tech community, he serves as a moderator at LUG-Jaipur. When he’s not leading engineering teams or exploring new technologies, you’ll find him on the open road on his bike, catching an action movie, or immersed in high-stakes FPS games.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.