What Is a Custom WordPress Plugin?
A WordPress plugin is a package of PHP code that extends the functionality of a WordPress site without touching core files. Instead of modifying wp-includes or your theme directly, plugins hook into WordPress at defined points using the action and filter system.
When an off-the-shelf plugin does not do exactly what you need, you build a custom one. I build custom plugins regularly for clients who need API integrations, custom post type management tools, admin dashboards, and WooCommerce extensions.
Setting Up Your Plugin File
Every WordPress plugin starts with a PHP file containing a specific header comment block. This tells WordPress the plugin exists and what it is called.
Create a folder in /wp-content/plugins/your-plugin-name/ and inside it create your-plugin-name.php:
<?php
/**
* Plugin Name: My Custom Plugin
* Plugin URI: https://yoursite.com/
* Description: A custom plugin that does something specific.
* Version: 1.0.0
* Author: Haris Maqsood
* Author URI: https://harismaqsood.com
* License: GPL v2 or later
* Text Domain: my-custom-plugin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Prevent direct access
}The ABSPATH check is critical for security. It prevents the file from being executed directly via URL.
Using WordPress Hooks: Actions and Filters
The WordPress hook system is how your plugin communicates with core. There are two types: actions and filters.
Actions let you run code at specific points. Filters let you modify data before it is used.
// Action: Run code when WordPress initializes
add_action( 'init', 'mcp_register_custom_post_type' );
function mcp_register_custom_post_type() {
register_post_type( 'project', [
'label' => 'Projects',
'public' => true,
'supports' => [ 'title', 'editor', 'thumbnail' ],
] );
}
// Filter: Modify the page title
add_filter( 'the_title', 'mcp_modify_title', 10, 2 );
function mcp_modify_title( $title, $post_id ) {
if ( get_post_type( $post_id ) === 'project' ) {
return 'Project: ' . $title;
}
return $title;
}Always prefix your function names with something unique to your plugin. This prevents collisions with other plugins or themes.
Adding an Admin Settings Page
Most plugins need a settings page so non-technical users can configure behavior without touching code.
add_action( 'admin_menu', 'mcp_add_settings_page' );
function mcp_add_settings_page() {
add_options_page(
'My Plugin Settings',
'My Plugin',
'manage_options',
'my-custom-plugin',
'mcp_render_settings_page'
);
}
function mcp_render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) return;
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields( 'mcp_settings_group' );
do_settings_sections( 'my-custom-plugin' );
submit_button();
?>
</form>
</div>
<?php
}Storing and Retrieving Options Safely
WordPress has a built-in options API. Use get_option and update_option to store plugin settings. Always sanitize input on save and escape output when rendering.
// Save a value
update_option( 'mcp_api_key', sanitize_text_field( $input ) );
// Retrieve and output it safely
$api_key = get_option( 'mcp_api_key', '' );
echo esc_html( $api_key );Security Checklist for Every Plugin
Every time I write a WordPress plugin, I check these four things before delivery:
Nonce verification: Any form submission or AJAX request must include a nonce to prevent cross-site request forgery.
// Generate nonce in your form
wp_nonce_field( 'mcp_save_action', 'mcp_nonce_field' );
// Verify it on form processing
if ( ! wp_verify_nonce( $_POST['mcp_nonce_field'], 'mcp_save_action' ) ) {
wp_die( 'Security check failed.' );
}Capability checks: Always verify the user has permission before performing sensitive actions.
Sanitization: Never trust user input. Use sanitize_text_field, absint, wp_kses_post, or the appropriate sanitization function for your data type.
Escaping output: Use esc_html, esc_url, esc_attr when outputting anything to the browser.
Best Practices I Follow on Every Plugin
After 5 years of building custom plugins for clients across the 20+ countries worldwide, a few habits have become non-negotiable for me:
One plugin, one responsibility. Keep your plugin focused. If it starts growing beyond a single purpose, consider splitting it.
Use classes and namespaces for anything beyond trivial functionality. This keeps code organized and reduces the risk of global namespace collisions.
Register all assets properly using wp_enqueue_scripts and wp_enqueue_style. Never echo or tags directly into the page.
Document your code. Future developers, including yourself six months from now, will thank you.
Need a custom WordPress plugin built to spec? I build bespoke plugins for clients worldwide. Get in touch and let's talk about what you need.