UnderHost
Knowledgebase Docs

WordPress Plugin Development: Build Custom Plugins

Learn WordPress plugin development basics: hooks, actions, filters, database access, security, and plugin structure. Extend WordPress functionality.

On this page

Plugins extend WordPress functionality without modifying core files. They let you add custom features: shortcodes, admin pages, database tables, REST API endpoints, and more. WordPress's hook system (actions & filters) makes this possible.

Plugin Basics

What plugins do:

  • Add new features (contact forms, testimonials, pricing tables)
  • Modify existing behavior (custom post types, taxonomies)
  • Integrate with external services (payment gateways, email services)
  • Improve performance (caching, optimization)
  • Enhance security (firewalls, spam protection)

Plugin vs. Theme: Plugins persist across theme changes; themes don't. Use plugins for functionality, themes for presentation.

Plugin File Structure

Minimum required:

/wp-content/plugins/my-awesome-plugin/
  └── my-awesome-plugin.php

Better structure (for larger plugins):

/wp-content/plugins/my-awesome-plugin/
  ├── my-awesome-plugin.php (main file with header)
  ├── includes/
  │   ├── class-plugin.php
  │   └── functions.php
  ├── admin/
  │   ├── admin-page.php
  │   └── admin-styles.css
  ├── public/
  │   ├── public-shortcodes.php
  │   └── public-styles.css
  ├── assets/
  │   ├── js/
  │   └── css/
  └── readme.txt

Main Plugin File (Header)

Every plugin needs this header in the main .php file:

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Plugin URI: https://yourdomain.com
 * Description: This plugin adds amazing features to WordPress
 * Version: 1.0.0
 * Author: Your Name
 * Author URI: https://yourdomain.com
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: my-awesome-plugin
 * Domain Path: /languages
 */

// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Your plugin code here
?>

Actions & Hooks

Actions are execution points where plugins inject code:

# Add custom code to WordPress header
add_action( 'wp_head', function() {
    echo '<meta name="custom">';
} );

# Run code when post is published
add_action( 'publish_post', function( $post_id ) {
    echo 'A post was published!';
} );

# Run code when plugin activates
register_activation_hook( __FILE__, function() {
    // Create custom database table
    global $wpdb;
    $wpdb->query( "CREATE TABLE ..." );
} );

# Run code when plugin deactivates
register_deactivation_hook( __FILE__, function() {
    // Clean up
} );

Common action hooks:

  • wp_head - WordPress head section
  • wp_footer - Footer
  • admin_menu - Admin sidebar menu
  • publish_post - When post published
  • user_register - When user registers

Filters

Filters modify data before output:

# Change post excerpt length
add_filter( 'excerpt_length', function() {
    return 25; // Show 25 words instead of default
} );

# Modify post title
add_filter( 'the_title', function( $title ) {
    return '★ ' . $title;
} );

# Filter post content
add_filter( 'the_content', function( $content ) {
    return $content . '<p>Added by my plugin!</p>';
} );

Database Access

Create custom table on plugin activation:

register_activation_hook( __FILE__, function() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE {$wpdb->prefix}my_data (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        name varchar(200) NOT NULL,
        created_at datetime NOT NULL,
        PRIMARY KEY (id)
    ) $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );
} );

Query custom table:

global $wpdb;
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}my_data" );
foreach ( $results as $row ) {
    echo $row->name;
}

Admin Pages

Add custom admin page (settings page):

add_action( 'admin_menu', function() {
    add_menu_page(
        'My Plugin Settings',     // Page title
        'My Plugin',              // Menu title
        'manage_options',         // Capability required
        'my-plugin',              // Menu slug
        'my_plugin_admin_page',   // Function to render page
        'dashicons-admin-generic' // Icon
    );
} );

// Render the admin page
function my_plugin_admin_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1>My Plugin Settings</h1>
        <form method="post">
            <?php wp_nonce_field( 'my_plugin_nonce' ); ?>
            <table class="form-table">
                <tr>
                    <th scope="row">Setting Name</th>
                    <td><input type="text" name="setting_name" /></td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

Security Best Practices

Always validate and sanitize user input:

// Validate nonce (prevent CSRF)
if ( ! wp_verify_nonce( $_POST['nonce_field'], 'my_action' ) ) {
    die( 'Security check failed' );
}

// Sanitize input
$username = sanitize_text_field( $_POST['username'] );
$email = sanitize_email( $_POST['email'] );

// Escape output (prevent XSS)
echo esc_html( $user_input );
echo esc_attr( $attribute_value );
echo wp_kses_post( $html_content );

Critical security checklist:

  • ✅ Check user capabilities: current_user_can( 'manage_options' )
  • ✅ Use nonces for forms: wp_nonce_field()
  • ✅ Sanitize all user input: sanitize_text_field(), sanitize_email()
  • ✅ Escape all output: esc_html(), esc_attr(), wp_kses_post()
  • ✅ Never trust $_GET, $_POST, $_REQUEST directly
  • ✅ Use prepared statements for database queries: $wpdb->prepare()

Distributing Plugins

Share on WordPress.org (free):

  1. Create WordPress.org account
  2. Go to Plugin Submit form
  3. Upload plugin ZIP, add description
  4. Wait for approval (24-48 hours)
  5. Your plugin appears in search

Sell premium plugins:

  • EDD (Easy Digital Downloads): Self-hosted licensing
  • Gumroad: Simple digital product selling
  • Freemius: Plugin freemium model with licensing

Create plugin readme.txt:

=== My Awesome Plugin ===
Contributors: yourname
Tags: feature, tag, tag
Requires at least: 5.0
Tested up to: 6.4
Stable tag: 1.0.0
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Short description of what your plugin does.

== Description ==
Longer description with details.

== Installation ==
1. Upload to /wp-content/plugins/
2. Activate in WordPress admin

== Frequently Asked Questions ==
= How do I use this? =
Instructions here.

== Changelog ==
= 1.0.0 =
* Initial release
Security is non-negotiable

Plugins with security vulnerabilities get removed from WordPress.org and damage your reputation. Always sanitize input, escape output, and check capabilities.

Related: WordPress security plugins-compare and configure | WordPress Child Themes: Customize Safely Without Losing Changes | Secure a website from hacking

Was this article helpful?

Need managed WordPress hosting?

Run WordPress on UnderHost managed hosting with performance tuning, SSL, backups, security guidance, and expert support.

Related articles

Back to WordPress