Custom Application Developer · Project Consultant · Database Engineer
I've noticed that a bunch of the how-to-write-a-plugin articles out there focus on demonstrating the minimum amount of code needed to get a plugin going. Not many focus on good plugin structure or convention. This tutorial explains how to create a class based WordPress plugin that makes sense.
WordPress plugins can be defined in a single file or a directory of files. Starting with a directory gives you more flexibility and allows you to use common structures and folder layouts between projects and will add a level of maintainability.
Generally, I use the following folder tree:
Every plugin begins with a docblock PHP comment describing the plugin. This comment describes the plugin and its licensing.
<?php /* Plugin Name: WP Plugin Template Plugin URI: http://fyaconiello.github.com/wp-plugin-template Description: A simple WordPress plugin template Version: 1.0 Author: Francis Yaconiello Author URI: http://www.yaconiello.com License: GPL2 */ /* Copyright 2012 Francis Yaconiello (email : francis@yaconiello.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
After the plugin definition comment, you need to define your main plugin logic. I prefer to write class based plugins. In my case, a class named WP_Plugin_Template. WP_Plugin_Template must have several methods. In the class constructor register all of the actions that your base plugin needs. There are also static class functions activate and deactivate that get called on plugin activation/deactivation. We'll be adding a bunch to this later, but at a very basic level, this is what your plugin class consists of.
<?php if(!class_exists('WP_Plugin_Template')) { class WP_Plugin_Template { /** * Construct the plugin object */ public function __construct() { // register actions } // END public function __construct /** * Activate the plugin */ public static function activate() { // Do nothing } // END public static function activate /** * Deactivate the plugin */ public static function deactivate() { // Do nothing } // END public static function deactivate } // END class WP_Plugin_Template } // END if(!class_exists('WP_Plugin_Template'))
At the bottom of the plugin class register the activation/deactivation hooks and instantiate the plugin class by calling the constructor.
<?php if(class_exists('WP_Plugin_Template')) { // Installation and uninstallation hooks register_activation_hook(__FILE__, array('WP_Plugin_Template', 'activate')); register_deactivation_hook(__FILE__, array('WP_Plugin_Template', 'deactivate')); // instantiate the plugin class $wp_plugin_template = new WP_Plugin_Template(); }
To add a settings page we need to start adding actions and callbacks to the main plugin class.
In your main plugin class's contructor function, public function __construct(), add the following code to hook into the admin_init and admin_menu actions.
<?php add_action('admin_init', array(&$this, 'admin_init')); add_action('admin_menu', array(&$this, 'add_menu'));
The above code calls the current class instance's admin_init and add_menu functions. In order for these callbacks to work, you need to add those functions:
<?php /** * hook into WP's admin_init action hook */ public function admin_init() { // Set up the settings for this plugin $this->init_settings(); // Possibly do additional admin_init tasks } // END public static function activate
When you register plugin specific settings, make sure to namespace them with your plugin name to avoid clashes with other plugins. see http://codex.wordpress.org/Function_Reference/register_setting
<?php /** * Initialize some custom settings */ public function init_settings() { // register the settings for this plugin register_setting('wp_plugin_template-group', 'setting_a'); register_setting('wp_plugin_template-group', 'setting_b'); } // END public function init_custom_settings()
Add your menu with a callback to a class function. Do not embed HTML into your menu page's callback function. Remember the directory structure I suggested above? put your settings HTML into a settings.php file in that directory. see http://codex.wordpress.org/Function_Reference/add_options_page
<?php /** * add a menu */ public function add_menu() { add_options_page('WP Plugin Template Settings', 'WP Plugin Template', 'manage_options', 'wp_plugin_template', array(&$this, 'plugin_settings_page')); } // END public function add_menu() /** * Menu Callback */ public function plugin_settings_page() { if(!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } // Render the settings template include(sprintf("%s/templates/settings.php", dirname(__FILE__))); } // END public function plugin_settings_page()
In your settings.php template file use the name-spaced settings group name that you created above as arguments for several of the functions. Those functions handle all validation and storage for this settings page. see http://codex.wordpress.org/Function_Reference/settings_fields and http://codex.wordpress.org/Function_Reference/do_settings_fields
<div class="wrap"> <h2>WP Plugin Template</h2> <form method="post" action="options.php"> <?php @settings_fields('wp_plugin_template-group'); ?> <?php @do_settings_fields('wp_plugin_template-group'); ?> <table class="form-table"> <tr valign="top"> <th scope="row"><label for="setting_a">Setting A</label></th> <td><input type="text" name="setting_a" id="setting_a" value="<?php echo get_option('setting_a'); ?>" /></td> </tr> <tr valign="top"> <th scope="row"><label for="setting_b">Setting B</label></th> <td><input type="text" name="setting_b" id="setting_b" value="<?php echo get_option('setting_b'); ?>" /></td> </tr> </table> <?php @submit_button(); ?> </form> </div>
Lastly, to add a settings link next to the plugin's activate/deactivate link in the Plugin Management list add the following to the bottom of your main plugin file directly after the $wp_plugin_template = new WP_Plugin_Template(); line that instantiates your plugin class.
<?php // Add a link to the settings page onto the plugin page if(isset($wp_plugin_template)) { // Add the settings link to the plugins page function plugin_settings_link($links) { $settings_link = '<a href="options-general.php?page=wp_plugin_template">Settings</a>'; array_unshift($links, $settings_link); return $links; } $plugin = plugin_basename(__FILE__); add_filter("plugin_action_links_$plugin", 'plugin_settings_link'); }
For every custom post type you want to add to your plugin you need to define a post type class. For this example, wp-plugin-name/post-types/post_type_template.php.
In this class add a constant POST_TYPE this is the name that your post type will be registered as. Also, add a private array $_meta this is an array of all the post types additional metafield names.
<?php if(!class_exists('PostTypeTemplate')) { /** * A PostTypeTemplate class that provides 3 additional meta fields */ class PostTypeTemplate { const POST_TYPE = "post-type-template"; private $_meta = array( 'meta_a', 'meta_b', 'meta_c', ); } // END class PostTypeTemplate } // END if(!class_exists('PostTypeTemplate'))
This class needs a constructor to set up the initial action hooks.
<?php /** * The Constructor */ public function __construct() { // register actions add_action('init', array(&$this, 'init')); add_action('admin_init', array(&$this, 'admin_init')); } // END public function __construct()
In our post-type class's init() function, call a function to create the post type, and set up a custom function to hook into save_post. see http://codex.wordpress.org/Function_Reference/register_post_type and http://codex.wordpress.org/Plugin_API/Action_Reference/save_post and http://codex.wordpress.org/Function_Reference/update_post_meta
<?php /** * hook into WP's init action hook */ public function init() { // Initialize Post Type $this->create_post_type(); add_action('save_post', array(&$this, 'save_post')); } // END public function init() /** * Create the post type */ public function create_post_type() { register_post_type(self::POST_TYPE, array( 'labels' => array( 'name' => __(sprintf('%ss', ucwords(str_replace("_", " ", self::POST_TYPE)))), 'singular_name' => __(ucwords(str_replace("_", " ", self::POST_TYPE))) ), 'public' => true, 'has_archive' => true, 'description' => __("This is a sample post type meant only to illustrate a preferred structure of plugin development"), 'supports' => array( 'title', 'editor', 'excerpt', ), ) ); } /** * Save the metaboxes for this custom post type */ public function save_post($post_id) { // verify if this is an auto save routine. // If it is our form has not been submitted, so we dont want to do anything if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if($_POST['post_type'] == self::POST_TYPE && current_user_can('edit_post', $post_id)) { foreach($this->_meta as $field_name) { // Update the post's meta field update_post_meta($post_id, $field_name, $_POST[$field_name]); } } else { return; } // if($_POST['post_type'] == self::POST_TYPE && current_user_can('edit_post', $post_id)) } // END public function save_post($post_id)
Add metaboxes to our custom post type's admin pages on admin_init call.
<?php /** * hook into WP's admin_init action hook */ public function admin_init() { // Add metaboxes add_action('add_meta_boxes', array(&$this, 'add_meta_boxes')); } // END public function admin_init() /** * hook into WP's add_meta_boxes action hook */ public function add_meta_boxes() { // Add this metabox to every selected post add_meta_box( sprintf('wp_plugin_template_%s_section', self::POST_TYPE), sprintf('%s Information', ucwords(str_replace("_", " ", self::POST_TYPE))), array(&$this, 'add_inner_meta_boxes'), self::POST_TYPE ); } // END public function add_meta_boxes()
Our post type's inner meta box includes a template file to keep metabox templating out of the business logic.
<?php /** * called off of the add meta box */ public function add_inner_meta_boxes($post) { // Render the job order metabox include(sprintf("%s/../templates/%s_metabox.php", dirname(__FILE__), self::POST_TYPE)); } // END public function add_inner_meta_boxes($post)
The template for include(sprintf("%s/../templates/%s_metabox.php", dirname(__FILE__), self::POST_TYPE)); in my case templates/post-type-template_metabox.php should look something like the following. Note: each of your posts meta fields should be represented here.
<table> <tr valign="top"> <th class="metabox_label_column"> <label for="meta_a">Meta A</label> </th> <td> <input type="text" id="meta_a" name="meta_a" value="<?php echo @get_post_meta($post->ID, 'meta_a', true); ?>" /> </td> <tr> <tr valign="top"> <th class="metabox_label_column"> <label for="meta_a">Meta B</label> </th> <td> <input type="text" id="meta_b" name="meta_b" value="<?php echo @get_post_meta($post->ID, 'meta_b', true); ?>" /> </td> <tr> <tr valign="top"> <th class="metabox_label_column"> <label for="meta_a">Meta C</label> </th> <td> <input type="text" id="meta_c" name="meta_c" value="<?php echo @get_post_meta($post->ID, 'meta_c', true); ?>" /> </td> <tr> </table>
Lastly, go back to the main plugin class's constructor. Before the end of the function call the following to incorporate your post type into the plugin.
<?php require_once(sprintf("%s/post-types/post_type_template.php", dirname(__FILE__))); $PostTypeTemplate = new PostTypeTemplate();
Congrats! you have created a class based WordPress plugin that does the following:
Download all of the codes here: https://github.com/fyaconiello/wp_plugin_template
#1 - WordPress is spelled like this: WordPress. Not wordpress, not Wordpress.
MichaelH on Feb. 1, 2013
#2 - The misspellings are fixed. If you have any additional suggestions/fixes or anything you'd like to add to the discussion let me know.
Francis Yaconiello on Feb. 1, 2013
#3 - Nice Information!!! thanks a lot!!!
Website Developement panipat,Sonepat,Karnal on Feb. 2, 2013
#4 - I absolutely love your blog and find many of your post's to be exactly I'm looking for. Do you offer guest writers to write content available for you? I wouldn't mind writing a post or elaborating on a lot of the subjects you write related to here. Again, awesome blog!
wordpress site design on Feb. 2, 2013
#5 - I am curious. Should I add a new directory and class for a custom taxonomy similar in form the the custom post type?
Chris Strutton on Feb. 4, 2013
#6 - @Chris Strutton - I'll add another mini-tutorial to walk through adding a custom taxonomy + add it to the github repo. But short answer: yes. /my_custom_plugin/taxonomies/my_custom_taxonomy.php. In your main plugin class's constructor: require_once(sprintf("%s/taxonomies/my_custom_taxonomy.php", dirname(__FILE__))); $MyCustomTaxonomy = new MyCustomTaxonomy(); The call to: register_taxonomy('tax_name', 'cpt_name') would be in my_custom_taxonomy.php's init method.
Francis Yaconiello on Feb. 4, 2013
#7 - Thanks for the quick reply. BTW, I was looking over your code and noticed that your coding standards don't match WordPress's. I have gone through and rearranged a lot of white space to match (mostly as a practice exercise). If you want I can send you a diff. (Note: this isn't a comment on your coding standards. I got to your page from the codex so it only seems appropriate that your example should fit the coding standards set out on the codex). Let me know if your interested.
Chris Strutton on Feb. 4, 2013
#8 - @Chris Strutton Sure. I'd love to update the repo/blog to adhere to WordPress's coding standards. I'm primarily a python dev these days, and before that I developed a bunch in Zend Framework. I'll admit that I dont switch my coding style to match each framework I end up working in, I'd have to keep abreast of over about 4 different PHP standards, 2 ruby conventions, and PEP-8 for python. I hope whitespace aside, the examples were helpful.
Francis Yaconiello on Feb. 4, 2013
#9 - What are the advantages of using this approach then using add_settings_field() and add_settings_section()?
Adrian B on Feb. 12, 2013
#10 - @Adrian B: I wrote up a little article to hopefully demystify the WordPress settings framework. http://yaconiello.com/blog/how-to-handle-wordpress-settings/. Short answer: the advantages of doing it the way I did in this example is ease of illustration (fewer callback functions to have to trace in order to understand the how the settings work). the disadvantage of doing this way is that you couldn't place your settings sections onto other pages.
Francis Yaconiello on Feb. 16, 2013
#11 - Nice, Thanks! Got me off to a good start on my plugin!
Yosuke Hasumi on March 6, 2013
#12 - @Yosuke Hasume - I'm glad it was helpful. If you need any additional clarification or examples let me know.
Francis Yaconiello on March 6, 2013
#13 - I'm relatively new to WP but I have a lot of experience with PHP and a few other languages (still not great with OO though). I'm having a hard time figuring out how to dynamically create posts using this example plugin framework. Does the $_meta need to be submitted with the wp_insert_post or do I need to call the save_post function after the fact and provide the $_meta to it? I am trying to import a bunch of posts from external DB data. The admins need to be able to import/update all the posts at once, I'm still working out the insert/update logic but that's not really part of the problem I am presently having. If you could provide a simple example of programmatically creating a new post w/ meta that would work with this example that would be epic... Thanks a ton for any input you can provide, WDP
William Papa on March 14, 2013
#14 - @William Papa Check this out & let me know if it helps - http://www.yaconiello.com/blog/intermediate-wordpress-examples-using-your-custom-post-type/
Francis Yaconiello on March 15, 2013
#15 - Thanks for the great tutorial. Whilst I have some experience in WordPress, I am new to plugin coding and OOP so following your directory structure / class based approach has been very informative. As an extra learning task for myself, I have added a custom taxonomy to the code. Unfortunately my knowledge of OOP is limited so I have not been able to implement the custom taxonomy as you suggested in an earlier comment. Basically as a work around I have modified post-type-template.php, in public function init() performed the add_action( 'init', 'my_custom_taxonomy', 0 ) and added an extra function my_custom_taxonomy() to do the register_taxonomy. Although this works, it does not conform to the structure laid out in your tutorial, which is my aim. Seeing how it would fit into your template would be most helpful to me and I am sure to others too. Would you be able to provide some further guidance on adding the custom taxonomy?
Arthur Ansell on March 19, 2013
#16 - CANNOT COPY & PASTE CODE! Aaargh!! Why would you do that?!
ANGRY BEAR on April 13, 2013
#17 - Hello I'm new to WordPress plugin development. Regarding post types, is it always necessary to create them for custom plugin?
Rahi Parsi on April 13, 2013
#18 - @Rahi Parsi, No it is not necessary to always have a custom post type for a plugin. However, it is a very common case, so I chose to use it as an example.
Francis Yaconiello on May 3, 2013
#19 - @ANGRY BEAR, Apologies for the copy paste problem, it wasn't intentional! apparently the ShareThis plugin I'm using for social sharing does that. I'll look into fixing it. In the mean time, all the code for this article is located here: https://github.com/fyaconiello/wp_plugin_template
Francis Yaconiello on May 3, 2013
#20 - gratefull and helping articles for me as a newbie
rahasia minisite on May 18, 2013
Fill out the form below to start a discussion.