Michael Silverman documenting innovation at work


How to Create a WordPress Plugin

Mike Example Plugin admin page.

Before creating a plugin it's helpful to determine the tools you need for your plugin to function and then determine how to integrate them into WordPress. In this guide I will be teaching by example. The plugin we will be creating is a simple post text replacement plugin. In the WP Admin panel we will be able to modify the search and replace parameters. We also will have a replacement color and toggle color. After some planning I've determined this plugin will require the following:

  1. jQuery : We need jQuery to toggle the class on our replacement text when clicked.
  2. Database : We need our settings stored permanently. Of course you already have a database set up for WordPress. We will use that.
  3. CSS : We will use two stylesheets, one for the admin side and one for the client side.
  4. Custom Javascript: We're going to place our code in a separate file to show you how to load JavaScript on the admin side and the client side.

As mentioned before we want the plugin to have an admin panel accessible from the WordPress panel. We also need to hook into posts/pages for our search matching.
The following are WordPress documents that are essential for reference when creating a plugin. It will prove useful to have them bookmarked and opened in your browser at all times.

WordPress Plugin Documents

  1. Plugin Resources: A top level page with links to all WordPress plugin related articles.
  2. Function Reference : List of all the WordPress functions, how to use them and what they do. Through these functions, mainly add_action, we are given a way to insert our own code into various points of WordPress' internal processes.
  3. Plugin API/Action Reference : List of all the add_action hooks within WordPress. One example that we will be using is admin_menu. We will use this to add a new menu option in the the admin panel.
  4. wp_enqueue_script : Scroll down to the bottom of this page. There is a list called "Default scripts included in WordPress." As it turns out  jQuery is included in WordPress. Though we still need to ensure that it is called for our page, we have one less script to worry about integrating. Check if your script is included before you worry about downloading it.

Create Plugin Directory and Files

We are going to name our plugin Mikex. Make a directory called mikex. Place it in the  wp-content/plugins/ directory. Within wp-content/plugins/mikex/ create these files:

  • mikex.php : This will contain all of our hooks into WordPress. When our plugin is activated WordPress will look at this page before a page is loaded and execute all the code.
  • mikex.js : JavaScript to run client side
  • mikex.css : CSS used client side
  • admin.php : This will be our admin page in the WordPress control panel.
  • admin.js : JavaScript to run on admin side
  • admin.css : CSS used on admin side
  • update.php : Connects our admin.php changes to the database
  • functions.php : All of our functions are defined here.

The Code

Each file is done separately. The code is commented well and any additional explanation is after the code. There are also links after each code block to the functions that were used.


For WordPress to recognize our plugin there is an obligatory header.

Plugin Name: Mike Example
Plugin URI: http://www.msilverman.me
Description: Example plugin for web tutorial
Version: 1.0
Author: Mike Silverman
Author URI: http://www.msilverman.me
License: GPl2

In this file, if the plugin is activated, any code outside a function is executed when any WordPress page is loaded. For example if you put echo "hello" in the start of this file, you would see "hello" on every WordPress generated page. WordPress page content is generated at run time, it is not stored. That is why we will be storing our hooks into the posts and menu in this file outside of a function.

When the user activates the plugin we need a few things to happen. The first thing that needs to be done is allocate space in the database. At this point you have a decision to make:

In one direction we can use the functions add_option and get_option for all database interaction. The advantage of this method is you do not need to be familiar with database structure or query syntax. The option values are stored in longtext format which is approximately 4GB of text. If you are working with an array of data it will automatically be serialized to a storable format.

The other option is to use the dbDelta function, see Creating Tables with Plugins. This allows you to create your own table. If you are familiar with database structure, or want more control, this is the method to use. I'm going to assume that you are not a database expert and for this example we really don't need any complex relationships. We will use the add_option/get_option for this tutorial.

/* We need to make sure our functions can be seen! */

include_once dirname(__FILE__) . '/functions.php';

/* The following events are not saved and must be executed on each page load */

register_activation_hook( __FILE__, "mikex_activated");
register_deactivation_hook( __FILE__, "mikex_deactivated");

/* This action will call the function to create a menu button */
add_action('admin_menu', 'mikex_add_menu_page');

/* This will load our admin panel javascript and CSS */
add_action('admin_enqueue_scripts', 'mikex_admin_scripts');

/* This will load the scripts on the client side for interaction */
add_action('wp_enqueue_scripts', 'mikex_client_scripts');

/* This shortcode allows us to run a function on the content of each post
   before it is displayed */
$options = get_option('mikex_opts');
add_shortcode($options['search'], 'mikex_replace_keyword');

Helpful links: register_activation_hook - register_deactivation_hook  - add_action - add shortcode - get_option - admin_menu - admin_enqueue_scripts - wp_enqueue_scripts

The add_action function takes two parameters, an action hook and a callback function. A hook specifies at which point in the page loading process you need to manipulate. All of the hooks are found in the Plugin API/Action Reference. We want to modify the menu so we use the admin_menu hook. The action hook enqueue_scripts will load our CSS and JavaScript. admin_enqueue_scripts is used for for the admin side and wp_enqueue_scripts for the client side.

The add_shortcode function allows us to replace keywords in the post or page with content. The keyword WordPress will search for is in the form of the keyword surrounded by brackets: i.e. [keyword]. If I set the 'search' option in my example to "mikex" the keyword it will be searching for in the posts and pages is [mikex]. It will then use the return data of mikex_replace_keyword function to replace that keyword.

The callback functions we need to define later are mikex_activated, mike_deactivated, mikex_add_menu_page, mike_admin_scripts, mike_client_scripts, and mikex_replace_keyword.


This will change the color of our replaced text when it is clicked.

jQuery(document).ready(function() {
        //When the text span is clicked
        jQuery('.mikex-span').click(function() {

            //Define the newcolor
            newColor = jQuery(this).attr('newColor');

            //Change the color
            jQuery(this).css('color', newColor);


This file is more for you than me. All we are doing is making the font size larger.

.mikex-span {
    font-size: 3em;


This file will be what you view when you click your plugin name in the admin panel. WordPress will load the page inside a div where all the other content appears.

My example is very simple. We get an array of the set options from the database. We then create a form which will be posted to Update.php. Using jQuery we have text appear saying "Updated" after submission. The text then disappears if the user focuses on an input box. The CSS is used for the form styling.

<?php $options = get_option('mikex_opts'); ?>

<b>Mike Example!</b><br /><br />

<form id="mikex-settings" action="<?php echo plugins_url('update.php', __FILE__) ?>">

    <label>Search Text:</label>  <input name="search"  value="<?php echo $options['search'] ?>" type="text" /><br />
    <label>Replace Text:</label> <input name="replace" value="<?php echo $options['replace'] ?>" type="text" /><br />
    <label>Replace Color</label> <input name="replace-color" value="<?php echo $options['replace-color'] ?>" type="text" /><br />
    <label>Toggle Color:</label> <input name="toggle" value="<?php echo $options['toggle'] ?>" type="text" /><br />
    <input type="submit" value="Update" /><span class="update-status"></span>



 jQuery(document).ready(function() {

    jQuery('#mikex-settings').submit(function() {
        var postURL = jQuery(this).attr('action');
        var serializedSettings = jQuery(this).serialize();

        //Post the serialized form data into the action URL in the form
        jQuery.post(postURL, serializedSettings);

        //Update the form stated to 'Updated!'

        //We do not want our form submitted so we return false
        return false;

    //Remove the 'Updated!' status when an input is focused upon
    jQuery('#mikex-settings input').focus(function() {


Helpful Links: jQuery().serialize - jQuery().post - jQuery().text


The CSS used for the form.

#mikex-settings input {
    margin-bottom: 20px;
    width: 10em;
#mikex-settings label {
    display: inline-block;
    font-size: 1.2em;
    width: 8em;
#mikex-settings .update-status {
    margin-left: 20px;
    font-size: 0.8em;


This is how we connect our admin changes to the database. The code in here is very simple.


require_once(dirname( __FILE__ ) . '../../../../wp-blog-header.php');

/* We are assuming that if a "search" value is being posted all the data is
   being posted. */

if (key_exists('search', $_POST)) {
    update_option('mikex_opts', $_POST);


We must require the file wp-blog-header.php because that is where the update_option function is located. All the other code that we have used is called from a WordPress page so functions like update_option are already included.


/* Functions used by mikex.php */

function mikex_activated() {
	$mikex_version = 1.0;

/* These options are stored in the database. */
	add_option("mikex_version", $mikex_version);

	$default_opts = array('search' => 'searchtext',
			      'replace' => 'replacetext',
			      'replace-color' => 'replacecolortext',
			      'toggle' => 'togglecoor'
	add_option("mikex_opts", $default_opts);

function mikex_deactivated() {

/* We don't have anything to perform but you would put your deactivation
   functions here. */


/* add_menu_page creates a top level menu button. If you are looking for a
   menu button to place inside Settings use add_options_page */

function mikex_add_menu_page() {
    add_menu_page("Mike Example", "Mike Example", "activate_plugins", "mikex", "mikex_admin_page");

function mikex_admin_page() {
    include_once dirname(__FILE__) . '/admin.php';

/* Functions used by admin.php */

function mikex_admin_scripts() {

    /* Register and queue the Stylesheet */
    wp_register_style('mikex_admin_css', plugins_url('admin.css', __FILE__));

    /* Register and queue the Javascript */
    wp_register_script('mikex_admin_js', plugins_url('admin.js', __FILE__));

    /* Load the jQuery that is already included in WordPress */

function mikex_client_scripts() {

    /* Register and queue the Stylesheet */
    wp_register_style('mikex_css', plugins_url('mikex.css', __FILE__));

    /* Register and queue the Javascript */
    wp_register_script('mikex_js', plugins_url('mikex.js', __FILE__));

    /* Load the jQuery that is already included in WordPress */


/* Functions used for posts */
function mikex_replace_keyword() {

    /* Retrieve our settings */
    $options = get_option('mikex_opts');

    /* If we want to create more complex html code it's easier to capture the output buffer and return it */
    ob_start(); ?>
        <span class="mikex-span" newColor="<?php echo $options['toggle'] ?>" style="color: <?php echo $options['replace-color'] ?>;"><?php echo stripslashes($options['replace']) ?></span>
    /* Return the buffer contents into a variable */
    $new_content = ob_get_contents();

    /* Empty the buffer without displaying it. We don't want the previous html shown */

    /* The text returned will replace our shortcode matching text */
    return $new_content;


Helpful Links: ob_start - ob_get_contents - ob_end_clean - wp_register_script - wp_enqueue_script - wp_register_style - wp_enqueue_style - plugins_url

In mikex_activated the add_option does not do anything if the option exists. Our mikex_opts option will not be deleted if the user is activating/deactivating our plugin.

In mikex_add_menu_page it is important to pay attention to the activate_plugins parameter. This is where you define who can see the menu. I only want someone who can activate a plugin to be able to modify the plugin settings. To see the other options view the Roles and Capabilities. There is another function called mikex_admin_page which is the actual code that will be displaying in the admin page. Rather than defining it here we're going to include the admin.php file we created earlier.

When adding JavaScript or CSS we must first make sure the script or style is registered and then queue it to be loaded.  Make sure for a style you are using the wp_register_style and wp_register_script for the script. They look the same at a quick glance. We did not need to register jQuery because we found earlier that is is included with WordPress. Using the name specified in the wp_enqueue_script we just need to queue it.

In the mikex_replace_keyword function what is returned will replace the keyword we specified earlier. It is much easier and cleaner to us ob_start and specify the php/html then doing things like echo "<span class=\"mikex-span\">".


You now have a strong foundation for creating a WordPress plugin! I hope this was helpful. I can answer any questions and try to help those struggling. Leave a comment or e-mail me.

I'm using the plugin here, Click Me! I know you want to.

If you want to use my zip file as a base and work from there you can download it here mikex_plugin.

Comments (3) Trackbacks (0)
  1. WOW! Very complex!!

  2. Thanks for sharing, Mike!

  3. it works great …. but on activation it gives ERROR :: The plugin generated 1231 characters of unexpected output during activation. If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.

Leave a comment

No trackbacks yet.