CodeBodh

How to Create a Custom WordPress Plugin

Learn how to create a custom WordPress plugin from scratch, including plugin structure, hooks, settings, security, activation, uninstall, testing, and best practices.

Pardeep Patel By Pardeep Patel
Sep 28, 2026
5 min read
46 views
How to Create a Custom WordPress Plugin

WordPress is more than a content management system. Its plugin architecture allows developers to extend the platform with completely new functionality without modifying WordPress core.

You can build a small plugin that adds a single feature, or a much larger application containing custom post types, REST APIs, admin dashboards, database tables, integrations, automation, and more.

In this guide, we'll build a simple custom WordPress plugin from scratch and cover the fundamentals you need to turn that first plugin into a properly structured, secure, and maintainable project.


What Is a WordPress Plugin?

A WordPress plugin is a collection of code that extends WordPress functionality. Plugins can contain PHP code as well as JavaScript, CSS, images, and other assets. A plugin can be as simple as a single PHP file or can grow into a complete application with many files and directories.

For example, a plugin could add:

  1. A contact form
  2. A custom post type
  3. A booking system
  4. An SEO feature
  5. A membership system
  6. A payment integration
  7. A custom admin dashboard
  8. An API integration
  9. A CRM
  10. An e-commerce feature
  11. An automation system

The important principle is that you should extend WordPress through plugins rather than modifying WordPress core files. WordPress can overwrite core files during updates, which is why custom functionality belongs in plugins.


What We're Going to Build

For this tutorial, we'll create a simple plugin called:

CodeBodh Welcome Message

The plugin will:

  1. Add a new settings page to WordPress Admin.
  2. Allow the administrator to enter a custom message.
  3. Display that message on the website.
  4. Use WordPress hooks.
  5. Store the setting using the WordPress Options API.
  6. Include activation and deactivation handling.
  7. Follow basic WordPress security practices.

The same architecture can later be expanded into a much larger plugin.


Step 1: Create the Plugin Directory

Open your WordPress installation and navigate to:

wp-content/plugins/

Create a new directory:

codebodh-welcome

Inside that directory, create:

codebodh-welcome.php

Your initial structure is:

codebodh-welcome/
└── codebodh-welcome.php

WordPress detects plugins by looking for PHP files containing the appropriate plugin header. The official WordPress documentation recommends giving a plugin its own directory and using a main PHP file for the plugin.


Step 2: Add the Plugin Header

Open:

codebodh-welcome.php

Add:

<?php
/**
* Plugin Name: CodeBodh Welcome
* Description: A simple custom WordPress plugin built for CodeBodh.
* Version: 1.0.0
* Author: CodeBodh
* License: GPL-2.0-or-later
*/

The most important field is:

Plugin Name

WordPress uses this header information to recognize and display the plugin in the Plugins screen.

Additional header fields can include:

  1. Plugin URI
  2. Description
  3. Version
  4. Requires at least
  5. Requires PHP
  6. Author
  7. Author URI
  8. License
  9. License URI
  10. Text Domain
  11. Update URI
  12. Requires Plugins

WordPress documents these header fields in its current Plugin Handbook.


Step 3: Check That WordPress Detects the Plugin

Log in to:

WordPress Admin
→ Plugins

You should see:

CodeBodh Welcome

At this point the plugin doesn't do anything yet.

That's normal.

We have created the plugin's foundation.


Step 4: Understanding WordPress Hooks

Hooks are one of the most important concepts in WordPress development.

They allow your plugin to interact with WordPress at predefined points without modifying WordPress core.

There are two major types:

Actions

Actions allow your code to perform an operation when a particular event occurs.

For example:

add_action( 'init', 'codebodh_initialize_plugin' );

Filters

Filters allow your plugin to modify data before WordPress uses or displays it.

For example:

add_filter( 'the_content', 'codebodh_modify_content' );

The distinction is simple:

Action → do something

Filter → modify something

WordPress uses actions and filters extensively throughout its core functionality.


Step 5: Add a Simple Action

Let's add a basic initialization function:

function codebodh_welcome_init() {
// Plugin initialization code.
}

add_action( 'init', 'codebodh_welcome_init' );

The function doesn't need to do anything yet.

The important thing is understanding the relationship:

WordPress
↓
init hook
↓
codebodh_welcome_init()
↓
Your plugin code

This is the basic pattern you'll use repeatedly when developing WordPress plugins.


Step 6: Create a Plugin Setting

Now let's create something useful.

We'll allow the administrator to configure a welcome message.

For example:

Welcome to CodeBodh!

WordPress provides the Options API for storing plugin settings and other configuration data. The Plugin Handbook specifically recommends using WordPress APIs rather than reinventing functionality that WordPress already provides.

We'll store our message using:

update_option()

and retrieve it using:

get_option()


Step 7: Add an Admin Menu

Add the following:

function codebodh_welcome_menu() {

add_options_page(
'CodeBodh Welcome',
'CodeBodh Welcome',
'manage_options',
'codebodh-welcome',
'codebodh_welcome_settings_page'
);
}

add_action( 'admin_menu', 'codebodh_welcome_menu' );

This creates a settings page under:

Settings
└── CodeBodh Welcome

The capability:

manage_options

helps ensure that only users with the appropriate administrative capability can access the settings page.


Step 8: Create the Settings Page

Now create the callback:

function codebodh_welcome_settings_page() {
?>
<div class="wrap">
<h1>CodeBodh Welcome</h1>

<form method="post" action="options.php">

<?php
settings_fields( 'codebodh_welcome_settings' );
do_settings_sections( 'codebodh-welcome' );
submit_button();
?>

</form>
</div>
<?php
}

This provides the basic administrative interface.

But we still need to register the setting and create the field.


Step 9: Register the Setting

Add:

function codebodh_welcome_settings() {

register_setting(
'codebodh_welcome_settings',
'codebodh_welcome_message'
);

add_settings_section(
'codebodh_welcome_section',
'Welcome Message',
'__return_false',
'codebodh-welcome'
);

add_settings_field(
'codebodh_welcome_message',
'Message',
'codebodh_welcome_message_field',
'codebodh-welcome',
'codebodh_welcome_section'
);
}

add_action( 'admin_init', 'codebodh_welcome_settings' );

Now create the field:

function codebodh_welcome_message_field() {

$message = get_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);

?>
<input
type="text"
name="codebodh_welcome_message"
value="<?php echo esc_attr( $message ); ?>"
class="regular-text"
/>
<?php
}

We now have a complete settings field.


Step 10: Display the Message

Now let's display the message on the website.

One simple approach is to use wp_footer:

function codebodh_display_welcome() {

$message = get_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);

if ( empty( $message ) ) {
return;
}

echo '<div class="codebodh-welcome">';
echo esc_html( $message );
echo '</div>';
}

add_action( 'wp_footer', 'codebodh_display_welcome' );

The message will now appear near the bottom of the website.


Why Escaping Is Important

Notice this:

esc_html( $message )

and earlier:

esc_attr( $message )

These functions help ensure that data is safely output in its intended context.

WordPress's security documentation recommends validating and sanitizing incoming data and escaping data when it is output. It also emphasizes that developers should not trust data simply because it came from an administrator, a database, or another external source.

A useful rule is:

Validate early, sanitize when appropriate, and escape when outputting.


Step 11: Use a Nonce for Custom Forms

When your plugin creates its own form-processing logic, use WordPress nonces to help protect against unauthorized requests.

For example:

wp_nonce_field(
'codebodh_save_settings',
'codebodh_nonce'
);

Then verify the nonce before processing the request.

For example:

if (
! isset( $_POST['codebodh_nonce'] ) ||
! wp_verify_nonce(
sanitize_text_field(
wp_unslash( $_POST['codebodh_nonce'] )
),
'codebodh_save_settings'
)
) {
return;
}

WordPress's plugin security guidance specifically covers nonces, validation, sanitization, and escaping as core parts of secure plugin development.


Step 12: Validate User Input

Sanitization is not the same thing as validation.

Suppose your plugin expects:

Number of products = integer

You should validate that the value is actually an integer.

If the plugin expects:

Website URL

validate that it is a valid URL.

If it expects:

Email address

validate it as an email address.

WordPress recommends validation where possible and sanitization when validation isn't sufficiently specific.


Step 13: Add Activation Logic

Plugins can perform setup tasks when they are activated.

WordPress provides:

register_activation_hook()

For example:

function codebodh_welcome_activate() {

if ( false === get_option( 'codebodh_welcome_message' ) ) {

add_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);
}
}

register_activation_hook(
__FILE__,
'codebodh_welcome_activate'
);

Activation hooks can be used for tasks such as:

  1. Creating default settings
  2. Creating database tables
  3. Registering initial configuration
  4. Refreshing rewrite rules

WordPress documents activation and deactivation hooks as the appropriate mechanism for plugin lifecycle operations.


Step 14: Add Deactivation Logic

You can also run cleanup tasks when the plugin is deactivated.

function codebodh_welcome_deactivate() {

// Remove temporary data if required.
}

register_deactivation_hook(
__FILE__,
'codebodh_welcome_deactivate'
);

But there is an important distinction:

Deactivation is not the same as uninstalling.

Deactivation generally means:

Stop the plugin from running.

Uninstallation means:

Remove the plugin and its stored data.

WordPress specifically recommends using the uninstall process for permanent cleanup rather than the deactivation hook.


Step 15: Create an Uninstall File

Create:

uninstall.php

Your plugin now looks like:

codebodh-welcome/
│
├── codebodh-welcome.php
└── uninstall.php

Inside uninstall.php:

<?php

if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}

delete_option( 'codebodh_welcome_message' );

This allows WordPress to remove plugin-specific data when the administrator deletes the plugin.

The WordPress Plugin Handbook documents both uninstall.php and register_uninstall_hook() as supported approaches.


Step 16: Organize a Larger Plugin Properly

A one-file plugin is fine for a very small project.

But as functionality grows, you should separate the code.

A larger plugin could look like:

codebodh-plugin/
│
├── codebodh-plugin.php
├── uninstall.php
│
├── includes/
│ ├── class-plugin.php
│ ├── class-settings.php
│ └── class-database.php
│
├── admin/
│ ├── class-admin.php
│ ├── css/
│ │ └── admin.css
│ └── js/
│ └── admin.js
│
├── public/
│ ├── class-public.php
│ ├── css/
│ │ └── public.css
│ └── js/
│ └── public.js
│
├── languages/
│
├── templates/
│
└── assets/
└── images/

WordPress's Plugin Handbook recommends organizing larger plugins into logical directories such as includes, admin, public, languages, and related asset directories.


Step 17: Use a Unique Prefix

Your plugin should avoid generic function names such as:

function save_settings() {}

Another plugin might already have a function with the same name.

Instead use a unique prefix:

function codebodh_save_settings() {}

For classes, constants, options, database tables, and hooks, use a consistent naming convention.

For example:

codebodh_
CB_
CodeBodh\

This reduces the possibility of naming conflicts with other plugins.


Step 18: Don't Put Business Logic Everywhere

A common beginner mistake is putting everything inside the main plugin file.

As the plugin grows, separate:

Bootstrap
Business Logic
Database
Admin UI
Frontend
API
Assets
Configuration

For example:

codebodh-plugin.php
↓
Plugin Loader
↓
┌──────┼────────┬──────────┐
↓ ↓ ↓ ↓
Admin Public Database API

This makes the application easier to maintain and update.


Step 19: Use WordPress APIs

Don't reinvent functionality unnecessarily.

WordPress already provides APIs for many common tasks, including:

  1. Options
  2. HTTP requests
  3. Settings
  4. Metadata
  5. Filesystem operations
  6. Database access
  7. Users
  8. Roles and capabilities
  9. Cron
  10. REST APIs
  11. Transients

The WordPress Plugin Handbook specifically recommends using WordPress APIs where appropriate rather than recreating functionality yourself.


Step 20: Create an Admin Dashboard

If your plugin has multiple features, consider creating a dedicated admin menu.

For example:

CodeBodh Tools
│
├── Dashboard
├── Settings
├── Products
├── Integrations
├── Logs
└── About

You can create a top-level menu using:

add_menu_page()

or a submenu using:

add_submenu_page()

For a small plugin, however, placing settings under the WordPress Settings menu may be more appropriate.

Don't create a large dashboard simply because you can.

The admin interface should match the complexity of the plugin.


Step 21: Add Custom Post Types When Appropriate

If your plugin needs to manage a specific type of content, a custom post type may be appropriate.

For example:

Products
Courses
Events
Properties
Books
Tickets
Reviews

A product plugin might create:

Product

with its own:

  1. Title
  2. Description
  3. Featured image
  4. Categories
  5. Metadata
  6. Custom fields

This allows WordPress's existing content-management infrastructure to handle much of the basic functionality.


Step 22: When Should You Create a Custom Database Table?

Not every plugin needs its own database table.

For simple configuration:

Options API

may be sufficient.

For content:

Custom Post Types

may be appropriate.

For large volumes of structured transactional data, a custom table may make more sense.

For example, a booking plugin might eventually need:

wp_codebodh_bookings

containing:

id
user_id
service_id
booking_date
start_time
end_time
status
created_at

Use the WordPress database API and $wpdb rather than directly manipulating database connections.


Step 23: Think About Security From the Beginning

Security shouldn't be added after the plugin is finished.

Build it into the architecture.

At minimum, consider:

Authentication

Check whether the current user has the required capability.

Authorization

Don't assume that being logged in means someone can perform every action.

Nonces

Protect state-changing requests.

Validation

Make sure submitted values match what your application expects.

Sanitization

Clean data where appropriate.

Escaping

Escape output according to its context.

SQL Safety

Use prepared queries when constructing SQL with variable data.

File Uploads

Validate file types, permissions, filenames, and upload behavior.

WordPress's official security documentation emphasizes that untrusted data must be treated carefully and that input should be validated/sanitized and output escaped.


Step 24: Test the Plugin

Before releasing your plugin, test more than the happy path.

Test:

Install
↓
Activate
↓
Configure
↓
Use
↓
Deactivate
↓
Reactivate
↓
Update
↓
Uninstall

Also test:

  1. Empty fields
  2. Invalid input
  3. Unauthorized users
  4. Missing permissions
  5. Duplicate data
  6. Failed API requests
  7. Database errors
  8. Plugin conflicts
  9. Theme conflicts
  10. Different PHP versions
  11. Different WordPress versions


Step 25: Test With a Clean WordPress Installation

One of the best tests is a fresh WordPress installation with:

  1. Default theme
  2. No unnecessary plugins
  3. Your plugin installed

This can reveal assumptions your plugin makes about the environment.

Then test against a more realistic environment containing other common plugins.


Step 26: Check for JavaScript and CSS Conflicts

If your plugin adds frontend assets, don't load them everywhere.

Instead of loading:

plugin.css
plugin.js

on every WordPress page, load them only where they are required.

For example:

Plugin page → Load plugin CSS/JS

Other pages → Don't load them

This reduces unnecessary frontend overhead.


Step 27: Internationalization

If you intend to distribute your plugin, consider internationalization from the beginning.

Instead of:

echo 'Welcome to CodeBodh';

use WordPress translation functions such as:

echo esc_html__(
'Welcome to CodeBodh',
'codebodh-welcome'
);

This makes the plugin easier to translate later.

Your plugin's Text Domain should match the appropriate plugin text domain. WordPress documents text domains as part of the plugin header and internationalization system.


Step 28: Add a Proper License

If you're distributing your plugin, clearly define the license.

The WordPress ecosystem is closely associated with GPL licensing, and WordPress.org's plugin directory requires compatible licensing for hosted plugins.

If you're creating a commercial plugin outside WordPress.org, make sure the license terms accurately describe what customers can and cannot do.

Don't assume that every commercial WordPress plugin uses the same licensing model.


Step 29: Package the Plugin

When you're ready to distribute the plugin, your ZIP file should contain the plugin directory:

codebodh-welcome.zip
│
└── codebodh-welcome/
├── codebodh-welcome.php
├── uninstall.php
├── includes/
├── admin/
├── public/
├── languages/
└── assets/

The user can then install it through:

WordPress Admin
→ Plugins
→ Add New Plugin
→ Upload Plugin


Step 30: If You Want to Sell the Plugin

A custom WordPress plugin can become a commercial digital product.

For example, you could offer:

Free Version

  1. Basic functionality
  2. Limited features
  3. Community documentation

Pro Version

  1. Advanced features
  2. Premium integrations
  3. Priority support
  4. Automatic updates

Or sell a standalone plugin with a license such as:

1 Site
5 Sites
Unlimited Sites
Agency

The exact licensing model depends on the product and business strategy.

This is also where a platform such as CodeBodh can become useful: a developer can build a WordPress plugin and potentially distribute it as a digital product rather than using it only on their own website.


A Simple Complete Plugin

At this point, our basic plugin can be structured around:

codebodh-welcome/
│
├── codebodh-welcome.php
└── uninstall.php

The main file contains:

<?php
/**
* Plugin Name: CodeBodh Welcome
* Description: A simple custom WordPress plugin.
* Version: 1.0.0
* Author: CodeBodh
* License: GPL-2.0-or-later
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

function codebodh_welcome_activate() {

if ( false === get_option( 'codebodh_welcome_message' ) ) {
add_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);
}
}

register_activation_hook(
__FILE__,
'codebodh_welcome_activate'
);


function codebodh_welcome_menu() {

add_options_page(
'CodeBodh Welcome',
'CodeBodh Welcome',
'manage_options',
'codebodh-welcome',
'codebodh_welcome_settings_page'
);
}

add_action(
'admin_menu',
'codebodh_welcome_menu'
);


function codebodh_welcome_settings() {

register_setting(
'codebodh_welcome_settings',
'codebodh_welcome_message',
array(
'sanitize_callback' => 'sanitize_text_field',
)
);

add_settings_section(
'codebodh_welcome_section',
'Welcome Message',
'__return_false',
'codebodh-welcome'
);

add_settings_field(
'codebodh_welcome_message',
'Message',
'codebodh_welcome_message_field',
'codebodh-welcome',
'codebodh_welcome_section'
);
}

add_action(
'admin_init',
'codebodh_welcome_settings'
);


function codebodh_welcome_message_field() {

$message = get_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);

?>
<input
type="text"
name="codebodh_welcome_message"
value="<?php echo esc_attr( $message ); ?>"
class="regular-text"
/>
<?php
}


function codebodh_welcome_settings_page() {
?>
<div class="wrap">

<h1>CodeBodh Welcome</h1>

<form method="post" action="options.php">

<?php

settings_fields(
'codebodh_welcome_settings'
);

do_settings_sections(
'codebodh-welcome'
);

submit_button();

?>

</form>

</div>
<?php
}


function codebodh_display_welcome() {

$message = get_option(
'codebodh_welcome_message',
'Welcome to CodeBodh!'
);

if ( empty( $message ) ) {
return;
}

echo '<div class="codebodh-welcome">';
echo esc_html( $message );
echo '</div>';
}

add_action(
'wp_footer',
'codebodh_display_welcome'
);

And:

<?php

if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}

delete_option(
'codebodh_welcome_message'
);

This is intentionally simple, but it demonstrates the basic architecture behind many WordPress plugins:

Plugin Header
↓
Initialization
↓
Hooks
↓
Admin Settings
↓
Database / Options
↓
Frontend Output
↓
Activation
↓
Deactivation
↓
Uninstall


Common Mistakes to Avoid

1. Editing WordPress Core

Don't modify WordPress core files to add your functionality. Core updates can overwrite those changes. Use plugins and hooks instead.

2. Using Generic Function Names

Avoid:

function settings() {}

Prefer:

function codebodh_settings() {}

3. Trusting User Input

Never assume submitted data is safe.

Validate and sanitize input and escape output appropriately.

4. Forgetting Uninstall Cleanup

If your plugin creates options or custom tables, decide what should happen when the user permanently deletes the plugin.

Use the uninstall lifecycle for permanent cleanup rather than deactivation.

5. Loading Assets Everywhere

Don't load large JavaScript and CSS files on every page if your plugin only needs them in one location.

6. Building Too Much Too Soon

A small plugin doesn't need a massive architecture.

Start simple.

Refactor when the complexity actually requires it.

WordPress itself recommends choosing plugin architecture appropriate to the size and complexity of the plugin.


Final Thoughts

Creating a WordPress plugin isn't simply about writing a PHP file.

A good plugin should:

  1. Follow WordPress conventions
  2. Use hooks appropriately
  3. Keep functionality separate from WordPress core
  4. Validate input
  5. Sanitize where appropriate
  6. Escape output
  7. Protect administrative actions
  8. Handle activation and deactivation correctly
  9. Clean up after uninstall
  10. Use a maintainable structure
  11. Consider compatibility
  12. Be tested before distribution

Once you understand these fundamentals, you can move far beyond a simple settings plugin.

The same concepts can be used to build:

Custom post type plugins

Membership systems

Booking platforms

CRM plugins

Payment integrations

SEO tools

AI integrations

Automation plugins

Analytics dashboards

WooCommerce extensions

and even complete WordPress-based SaaS products.

The key is to start small, understand the WordPress APIs and hooks, and gradually build your plugin architecture around the actual problem you're trying to solve.

Build the feature as a plugin. Build the plugin as a product.

Pardeep Patel

Creator and developer contributing high quality tutorials, guides and resources on CodeBodh.

Comments (0)

Security check against automated spam
10 - 7 = ?