How to Create a Custom WordPress Plugin: A Complete Guide
WordPress plugins make it possible to add new functionality to a website without modifying WordPress core files. While thousands of ready-made plugins are available, there are situations where creating your own lightweight plugin is a better solution.
A custom plugin can contain anything from a simple shortcode to advanced integrations, custom admin features, API connections, or automation.
This guide explains how to create a basic WordPress plugin from scratch and gradually expand it with practical examples.
What Is a WordPress Plugin?
A WordPress plugin is a collection of PHP files and other resources that extends WordPress functionality.
Plugins can be used to:
- Add custom features
- Create shortcodes
- Add custom post types
- Modify WordPress behavior
- Add dashboard functionality
- Create custom REST API endpoints
- Integrate external services
- Add custom widgets
- Automate website tasks
- Connect WordPress with third-party applications
One major advantage of plugins is that their functionality can remain available even when you change your WordPress theme.
Why Create a Custom WordPress Plugin?
Creating your own plugin can be useful when existing plugins don’t provide exactly what your website needs.
Common reasons include:
- You need a small custom feature.
- You want to avoid installing a large plugin for one simple task.
- You need functionality specific to your business.
- You want full control over the source code.
- You want functionality independent of your theme.
- You need a custom API or third-party integration.
For example, instead of adding custom functionality to a theme’s functions.php, you could create a plugin:
my-custom-plugin
This keeps the functionality separate from the theme.
What You Need
Before creating a plugin, you should have:
- A WordPress installation
- Access to your website files
- Basic PHP knowledge
- Basic understanding of WordPress hooks
- A text or code editor
You can manage files through:
- Hosting File Manager
- SFTP
- FTP
- Local WordPress development environment
Always create a backup before modifying production files.
Step 1: Create the Plugin Directory
WordPress plugins are stored in:
wp-content/plugins/
Create a new directory inside this folder.
For example:
wp-content/plugins/my-custom-plugin/
The directory name should be simple and descriptive.
For this example, we’ll use:
my-custom-plugin
Your structure will initially look like:
wp-content/
└── plugins/
└── my-custom-plugin/
Step 2: Create the Main Plugin File
Inside the plugin directory, create:
my-custom-plugin.php
The structure becomes:
my-custom-plugin/
└── my-custom-plugin.php
Open the PHP file and add a WordPress plugin header.
<?php
/*
Plugin Name: My Custom Plugin
Description: A simple custom WordPress plugin.
Version: 1.0.0
Author: Your Name
*/
The plugin header tells WordPress that this PHP file is a plugin.
Step 3: Add Your First Function
Now add a simple function below the plugin header.
function my_custom_message() {
return 'Welcome to my website!';
}
At this point, the function exists, but WordPress doesn’t automatically display it anywhere.
We need to connect it to WordPress using a hook or shortcode.
Step 4: Create a Custom Shortcode
A shortcode allows you to insert custom functionality into WordPress content.
Add:
function my_custom_message() {
return 'Welcome to my website!';
}
add_shortcode('custom_message', 'my_custom_message');
Now you can use this shortcode inside a WordPress post or page:
[custom_message]
WordPress will replace the shortcode with:
Welcome to my website!
This is one of the simplest examples of how a custom plugin can extend WordPress.
Step 5: Activate the Plugin
After uploading your plugin files, log in to the WordPress Dashboard.
Go to:
Plugins → Installed Plugins
You should see:
My Custom Plugin
Click:
Activate
Your plugin is now running.
Complete Basic Plugin Example
Your plugin file can now contain:
<?php
/*
Plugin Name: My Custom Plugin
Description: A simple custom WordPress plugin.
Version: 1.0.0
Author: Your Name
*/
function my_custom_message() {
return 'Welcome to my website!';
}
add_shortcode('custom_message', 'my_custom_message');
This is a complete working WordPress plugin.
Step 6: Add an Admin Notice
You can also use your plugin to display a message inside the WordPress Dashboard.
Add:
function my_custom_admin_notice() {
echo '<div class="notice notice-success"><p>My custom plugin is active.</p></div>';
}
add_action('admin_notices', 'my_custom_admin_notice');
After activating the plugin, you should see the message in the WordPress administration area.
Understanding WordPress Hooks
Hooks are fundamental to WordPress plugin development.
There are two main types:
Actions
Actions allow your plugin to execute code at specific points.
Example:
function my_custom_action() {
// Your code
}
add_action('wp_footer', 'my_custom_action');
This tells WordPress to execute the function when the wp_footer action occurs.
Filters
Filters allow you to modify existing data.
For example:
function my_custom_title($title) {
return $title . ' | My Website';
}
add_filter('the_title', 'my_custom_title');
The function receives the existing title, modifies it, and returns the result.
Step 7: Create a Custom Post Type
A plugin can also register custom post types.
For example, you could create a Services post type.
function my_register_services_post_type() {
register_post_type('services', array(
'labels' => array(
'name' => 'Services',
'singular_name' => 'Service'
),
'public' => true,
'menu_icon' => 'dashicons-admin-tools',
'supports' => array(
'title',
'editor',
'thumbnail'
),
'has_archive' => true,
'rewrite' => array(
'slug' => 'services'
)
));
}
add_action('init', 'my_register_services_post_type');
After activating the plugin, WordPress will provide a new Services section in the Dashboard.
Step 8: Organize a Larger Plugin
As your plugin grows, putting everything into one PHP file can become difficult to manage.
A larger plugin might use:
my-custom-plugin/
│
├── my-custom-plugin.php
├── includes/
│ ├── functions.php
│ └── admin.php
│
├── admin/
│ ├── settings.php
│ └── styles.css
│
├── public/
│ ├── frontend.js
│ └── frontend.css
│
└── assets/
└── images/
This structure separates different parts of the plugin and makes future development easier.
Step 9: Add CSS to Your Plugin
If your plugin adds a frontend component, you may need custom CSS.
Create:
public/frontend.css
Then enqueue it:
function my_plugin_enqueue_styles() {
wp_enqueue_style(
'my-plugin-style',
plugin_dir_url(__FILE__) . 'public/frontend.css',
array(),
'1.0.0'
);
}
add_action('wp_enqueue_scripts', 'my_plugin_enqueue_styles');
Using wp_enqueue_style() is preferable to inserting a <link> tag manually into your page.
Step 10: Add JavaScript
You can also add JavaScript functionality.
Create:
public/frontend.js
Then enqueue it:
function my_plugin_enqueue_scripts() {
wp_enqueue_script(
'my-plugin-script',
plugin_dir_url(__FILE__) . 'public/frontend.js',
array(),
'1.0.0',
true
);
}
add_action('wp_enqueue_scripts', 'my_plugin_enqueue_scripts');
This loads the JavaScript file through WordPress’s normal asset-management system.
Step 11: Add Plugin Settings
More advanced plugins often need a settings page.
For example, you could allow administrators to configure:
- API keys
- Company name
- Contact information
- Display options
- Feature toggles
WordPress provides the Settings API for creating administration settings.
A simple settings page might be registered using:
function my_plugin_settings_page() {
add_options_page(
'My Plugin Settings',
'My Plugin',
'manage_options',
'my-plugin',
'my_plugin_settings_html'
);
}
add_action('admin_menu', 'my_plugin_settings_page');
You can then create the HTML output in a separate function.
For production plugins, use WordPress’s Settings API rather than manually processing arbitrary form submissions.
Step 12: Sanitize User Input
If your plugin accepts information from users or administrators, don’t save raw input without processing it.
For example:
$name = sanitize_text_field($_POST['name']);
Other WordPress functions include:
sanitize_email()
sanitize_textarea_field()
sanitize_key()
absint()
The appropriate function depends on the type of data being handled.
Step 13: Escape Output
Data should also be escaped when displayed.
For example:
echo esc_html($name);
For URLs:
echo esc_url($url);
For HTML attributes:
echo esc_attr($value);
Input sanitization and output escaping serve different purposes and should both be considered when handling data.
Step 14: Use Nonces for Forms
If your plugin contains forms that perform actions, WordPress nonces can help protect against unauthorized requests.
For example:
wp_nonce_field('my_plugin_action', 'my_plugin_nonce');
When processing the request, verify the nonce:
if (
!isset($_POST['my_plugin_nonce']) ||
!wp_verify_nonce(
$_POST['my_plugin_nonce'],
'my_plugin_action'
)
) {
return;
}
For administrative operations, also check that the current user has the required capability.
For example:
if (!current_user_can('manage_options')) {
return;
}
Step 15: Prevent Direct File Access
A common practice in WordPress plugins is to prevent direct access to PHP files.
Add:
if (!defined('ABSPATH')) {
exit;
}
This prevents the file from being executed directly outside the WordPress environment.
For example:
<?php
if (!defined('ABSPATH')) {
exit;
}
/*
Plugin Name: My Custom Plugin
Description: A simple custom WordPress plugin.
Version: 1.0.0
*/
Step 16: Create an Uninstall Process
If your plugin stores data in the WordPress database, think about what should happen when the plugin is permanently removed.
For example, a plugin might create:
- Options
- Custom database tables
- Metadata
- Scheduled tasks
You can create an uninstall process that removes data when appropriate.
However, don’t automatically delete user data simply because a plugin is deactivated. Deactivation and uninstallation are different events.
Step 17: Test Your Plugin
Before using your plugin on a production website, test it carefully.
Check:
- Plugin activation
- Plugin deactivation
- Frontend pages
- WordPress Dashboard
- Shortcodes
- Forms
- Database operations
- Different user roles
- Mobile layouts
- PHP errors
- WordPress updates
- Plugin conflicts
If possible, test it on a staging website first.
Common WordPress Plugin Mistakes
1. Using Generic Function Names
Avoid:
function custom_function() {
}
Another plugin might already use the same name.
Instead, use a unique prefix:
function ehostpiya_custom_function() {
}
A consistent prefix reduces naming conflicts.
2. Editing WordPress Core
Don’t add custom functionality directly to WordPress core files.
Avoid modifying:
wp-admin/
wp-includes/
or core PHP files.
WordPress updates can overwrite these modifications.
3. Putting Everything in One File
A tiny plugin can use one file, but larger plugins should be divided into logical files and directories.
This makes maintenance easier.
4. Not Validating User Input
Never blindly trust information submitted through forms, URLs, cookies, or other external sources.
Validate and sanitize data before processing or storing it.
5. Forgetting Permission Checks
If a plugin performs administrative actions, verify that the current user has the necessary capability.
For example:
if (!current_user_can('manage_options')) {
return;
}
6. Not Escaping Output
Always consider the appropriate escaping function when displaying dynamic information.
For example:
echo esc_html($message);
7. Ignoring PHP Compatibility
Your plugin should be tested against the PHP versions supported by your target WordPress environment.
Avoid relying on features unavailable in the minimum PHP version you intend to support.
Custom Plugin vs Child Theme
A child theme and plugin serve different purposes.
| Feature | Custom Plugin | Child Theme |
|---|---|---|
| Website functionality | Excellent | Possible |
| Theme-specific styling | Possible | Excellent |
| Template overrides | Limited | Excellent |
| Survives theme changes | Yes | No |
| Custom shortcodes | Yes | Yes |
| Custom post types | Yes | Possible |
| API integrations | Yes | Possible |
| Custom PHP functions | Yes | Yes |
A useful rule is:
Use a child theme for theme-specific customization and a plugin for functionality that should remain independent of the theme.
Example: A Better Basic Plugin Structure
For a small but maintainable plugin:
my-custom-plugin/
│
├── my-custom-plugin.php
├── includes/
│ └── functions.php
├── admin/
│ └── settings.php
└── public/
├── css/
│ └── style.css
└── js/
└── script.js
The main plugin file can load the required components.
For example:
<?php
if (!defined('ABSPATH')) {
exit;
}
/*
Plugin Name: My Custom Plugin
Description: Custom functionality for WordPress.
Version: 1.0.0
*/
require_once plugin_dir_path(__FILE__) . 'includes/functions.php';
This approach becomes much easier to maintain as the plugin grows.
Best Practices for Custom WordPress Plugins
Keep these practices in mind:
- Use a unique plugin name and function prefix.
- Don’t modify WordPress core files.
- Use WordPress hooks whenever possible.
- Sanitize incoming data.
- Validate data before processing it.
- Escape dynamic output.
- Use nonces for relevant forms and requests.
- Check user capabilities before sensitive operations.
- Use WordPress APIs instead of reinventing existing functionality.
- Enqueue CSS and JavaScript properly.
- Keep the plugin modular as it grows.
- Document important functions.
- Test with different themes and plugins.
- Test on staging before production deployment.
- Keep dependencies and code updated.
Final Thoughts
Creating a custom WordPress plugin doesn’t require a large codebase. A basic plugin can consist of a single PHP file containing a plugin header and a few functions. As your requirements grow, you can separate the code into modules, add administration pages, create custom post types, enqueue assets, interact with APIs, and build more advanced functionality.
The most important principle is to keep website functionality separate from WordPress core and, where appropriate, from the active theme. By using plugins, hooks, WordPress APIs, proper security practices, and a well-organized file structure, you can build custom functionality that is easier to maintain and upgrade over time.