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 sectionwp_footer- Footeradmin_menu- Admin sidebar menupublish_post- When post publisheduser_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):
- Create WordPress.org account
- Go to Plugin Submit form
- Upload plugin ZIP, add description
- Wait for approval (24-48 hours)
- 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





















