How to Create a Packed Addons Plugin for WordPress
Keeping functionality separate from presentation is a WordPress theme development best practice. In this post you will learn how to do just that, providing your users with a packaged plugin unique to your themes.
Theme Development Logic
We’ve been selling WordPress themes on ThemeForest for over four years, and during this time we’ve learned many things which have helped us succeed in the marketplace. One of the key aspects of successful theme development is “correct theme development logic”.
The key benefit of separating development logic is production speed: the quicker you can create quality themes, the more income you can generate. Keeping functionality in a separate plugin is also helpful when it comes to updates. Imagine you have ten themes on ThemeForest and you want to add new functionality as an update. Having all your functionality in a single plugin means you only need to update it once across the board, otherwise even a small update becomes slow and potentially painful.
So if you want to create powerful themes and earn more money doing so, respect WordPress’ plugin and theme system.
Functionality Types
What kind of functionality might a premium theme contain? What sort of things should we be pulling into a separate plugin? Here’s a list of key components we typically keep separate from theme files:
- Theme options panel
- Page/Post extended custom fields
- Custom widgets
- Custom shortcodes
- Custom post types
- Custom extra functions
In this post we won’t be detailing how to create the components themselves, but we will explain how to pack them all into one addons plugin.
Let’s Begin
Go to your wp-content > plugins and create an empty folder with the name of your packed plugin. We recommend you use a self-explanatory name. For example, our addons plugin is called “ninzio-addons” (Ninzio being the name of our company).
Important: do not use an underscore in the folder name! Use a dash if needed.
Next, in that plugin folder create a php file with the exact same name as your folder. In our example that would be “ninzio-addons.php”. Again, no underscores please. Open that file and add the following DocBlock header comment:
1 |
/**
|
2 |
* Plugin Name: Your plugin name
|
3 |
* Plugin URI: your plugin url
|
4 |
* Text Domain: text-domain
|
5 |
* Domain Path: /languages/
|
6 |
* Description: Short description of the plugin
|
7 |
* Author: Author name
|
8 |
* Version: 1.0.0
|
9 |
* Author URI: author uri
|
10 |
*/
|
Let’s examine the details we added here:
- Plugin name: this should be short and descriptive.
- Plugin URI: you can paste your website address here.
- Text Domain: this is a very important parameter. You should name it the same way as your plugin folder and main file. With the text domain you and your users can translate plugin strings. Later we will look at how to translate a packed addons plugin (we find this is very important for our clients).
- Domain Path: this parameter is also very important for plugin translation. The domain path is relative to the language file folder. Go to your addons plugin folder and create an empty folder with the name “languages”. For now leave it empty; later we will create the language file.
- All other parameters “Author”, “Version”, “ Author URI” are reasonably self-explanatory.
Now, having created our addons plugin folder and the main file it’s time to configure our plugin.
Configuration
In the main plugin file, after the header comment, paste this snippet:
1 |
if ( ! defined( 'ABSPATH' ) ) { |
2 |
exit; // Exit if accessed directly |
3 |
}
|
This is for security reasons; it blocks direct access to the plugin file.
Right after that, add this code:
1 |
function your_addons_load_plugin_textdomain() { |
2 |
load_plugin_textdomain( 'ninzio-addons', false, dirname( plugin_basename(__FILE__) ) . '/languages/' ); |
3 |
}
|
4 |
|
5 |
add_action( 'plugins_loaded', 'your_addons_load_plugin_textdomain' ); |
Here we load our plugin textdomain–make sure that function name is correct. Our recommendation for naming functions is to use self-descriptive names with a prefix of your plugin. For example ninzio_addons
. And as this is a php function, we can use underscores instead of dashes.
Make sure you’re accurate when copying or typing the load_plugin_textdomain function. For the domain parameter enter the exact textdomain that we defined earlier. And for the plugin relative path parameter enter the path to the languages folder that we created earlier.
Plugin Directory Path
Let’s now define our plugin directory path; add this code:
1 |
define( 'your_addons', plugin_dir_path( __FILE__ )); |
Here we’re using your_addons
as plugin directory path. So far so good; we have our plugin created, now it is time to fill it with custom functionality.
Theme Options Panel
We won’t use this step to cover how to create an option panel for a theme–you can create a custom one, or do as we do; use an off-the-shelf theme option panel framework. If you are new to option panel frameworks we recommend reading Bonang Salemane’s articles on Redux Framework theme integration:
To add a theme option panel to your addons copy the option panel folder, in its entirely, into the addons plugin folder. Now we need to require several files to activate it:
1 |
if ( ! class_exists( 'ReduxFramework' ) && file_exists( your_addons . '/optionpanel/framework.php' ) ) { |
2 |
require_once('optionpanel/framework.php' ); |
3 |
}
|
4 |
|
5 |
if ( ! isset( $redux_demo ) && file_exists( your_addons . '/optionpanel/config.php' ) ) { |
6 |
require_once('optionpanel/config.php' ); |
7 |
}
|
In this code snippet we required reduxframework’s two main files: the framework.php file that manages the option panel functionality, and the config,php file which is responsible for option panel configurations. Our option panel files are located in an “optionpanel” folder placed inside the ninzio-addons plugin folder. Done.
Custom Functions
It’s time we included some custom functions. Create a file inside your addons plugin folder, and name it something like “addons-functons.php”. Put all your custom functions inside this file.
One thing to watch is that you use proper function naming conventions. Use descriptive function names with a unique prefix. For example:
1 |
function your_addons_profile_social_links (){......} |
Right after the theme framework files, require your custom functions file:
1 |
require_once('includes/addons-functions.php' ); |
Custom Widgets
And now, include some custom widgets. Create a folder with the name “widgets” inside your addons plugin folder, put all your custom widgets files inside that folder. Custom widgets file naming is not critical here, but it is recommended you use prefixes and dashes, not underscores.
For example, our custom Twitter widget file is called “ninzio-recent-tweets.php”. Likewise, our Mailchimp widget is called “ninzio-mailchimp.php”. Let’s include them like so:
1 |
require_once('widgets/ninzio-recent-tweets.php' ); |
2 |
require_once('widgets/ninzio-mailchimp.php' ); |
Again, we will not cover the actual custom widgets creation process; for that, check out Bonang Salemane’s post:
Custom Post Types and Taxonomies
If you want to add a portfolio, or events, or anything that is similar to WordPress’ regular posts but need to be separated from the theme, you should use custom post types. All our themes have custom post types included. Creating custom post types can be complex, so once again that falls beyond the scope of this tutorial, but I recommend you read Tom McFarlin’s:
For custom post types you should create a separate folder inside your addons plugin, for example “ninzio-projects”. And inside that folder place all your files that relate to your custom post types. The most important files here are the main files that create the custom post types, the single post file, and the loop/archive post file. Name your custom post type main file as you have named your custom post type folder, such as “ninzio-projects.php”. Put your custom post type code inside that file, then to activate your custom post type you’ll need to require the main file:
1 |
require_once('ninzio-projects/ninzio-projects.php' ); |
When you separate functionality like this, you should always consider your clients–more precisely, how they can extend/rewrite your custom post type template files (archive and single). Let’s suppose our custom post type name is “projects”. The single custom post type file should be called “single-projects.php” and the loop/archive file should be called “archive-projects.php”.
And if your custom post type also has custom taxonomies you should create a separate file for them too. Let’s call our taxonomy file “taxonomy-projects.php”. So now we have three files:
1 |
single-projects.php |
2 |
archive-projects.php |
3 |
taxonomy-projects.php |
Let’s make them re-writeable. Add these three functions to your main addons file:
1 |
function your_addons_projects_single_template($single_template) { |
2 |
|
3 |
global $post; |
4 |
|
5 |
if ($post->post_type == 'projects') { |
6 |
if ( $theme_file = locate_template( array ( 'single-projects.php' ) ) ) { |
7 |
$single_template = $theme_file; |
8 |
} else { |
9 |
$single_template = your_addons . 'projects/single-projects.php'; |
10 |
}
|
11 |
}
|
12 |
|
13 |
return $single_template; |
14 |
}
|
15 |
|
16 |
add_filter( "single_template", "your_addons_projects_single_template", 20 ); |
1 |
function your_addons_projects_archive_template($archive_template) { |
2 |
|
3 |
global $post; |
4 |
|
5 |
if ($post->post_type == 'projects') { |
6 |
if ( $theme_file = locate_template( array ( 'archive-projects.php' ) ) ) { |
7 |
$archive_template = $theme_file; |
8 |
} else { |
9 |
$archive_template = your_addons . 'projects/archive-projects.php'; |
10 |
}
|
11 |
}
|
12 |
|
13 |
return $archive_template; |
14 |
}
|
15 |
|
16 |
add_filter( "archive_template", "your_addons_projects_archive_template", 20 ); |
1 |
function your_addons_projects_taxonomy_template($taxonomy_template) { |
2 |
|
3 |
if (is_tax('projects-category')) { |
4 |
|
5 |
if ( $theme_file = locate_template( array ( 'taxonomy-projects.php' ) ) ) { |
6 |
$taxonomy_template = $theme_file; |
7 |
} else { |
8 |
$taxonomy_template = your_addons . 'projects/taxonomy-projects.php'; |
9 |
}
|
10 |
}
|
11 |
|
12 |
return $taxonomy_template; |
13 |
}
|
14 |
|
15 |
add_filter( "taxonomy_template", "your_addons_projects_taxonomy_template", 20 ); |
Change the name of these functions to have your unique prefix. The main logic here is to load the template files of the custom post type after checking whether a copy of it is present in the theme folder. When your client copies the custom post type template to the theme and extends or overwrites it, your addons plugin will load your client’s version of the custom post type file. So in this situation your core files inside the addons plugin are not changed, they are merely extended by your client. Any future updates of your addons plugin will not delete your client’s custom modifications.
Custom Scripts, Styles and Shortcodes
For custom scripts and styles we recommend you create separate folders and enqueue the files like you would in your theme’s “functions.php” file.
If you plan on adding custom shortcodes you’ll need to create and include your shortcodes file inside your addons folder. Create a folder with the name “shortcodes” and inside that folder create the file “yourprefix-shortcodes.php” (in our case: “ninzio-shortcodes.php”). In the “-shortcodes.php” file you should put all your custom shortcodes code.
As you’ll have gathered by now, we will not use this tutorial to cover the custom shortcodes creation process, but I recommend you read Siddharth’s tutorial:
Let’s include our custom shortcodes file:
1 |
require_once('shortcodes/ninzio-shortcodes.php' ); |
Languages
We are almost done with the addons plugin. After you’ve tested all your custom functionality it is time to create the language file to make your plugin translatable.
Go to the addons plugin folder > languages. Use the PoEdit software (PoEdit is a free application, available for Mac, Windows and Linux.) to generate the language file. Again, be very accurate with the file naming; the resultant files should be named exactly as your addons plugin folder. For example, our plugin name is “ninzio-addons”, the language file should be “ninzio-addons.pot”. This is the main language file that contains all the strings within your textdomain. From this file you can create other language files.
To translate your addons plugin language file:
- Start up Poedit.
- In Poedit go to File > New from POT/PO file...
- Select and Open the pot file from the languages folder.
- Enter your name, email address, your language and country (i.e. French fr_FR, German de_DE).
- Click the Update button in the main Poedit UI.
- Save the file, named like “filename-xx_XX.po” with “xx_XX” for your language and country.
- That’s it!
Conclusion
Now your plugin is ready, well done! We’ve pulled all the necessary functionality away from our presentation files, and provided a packed plugin which we can update across multiple themes.
I hope you enjoyed following along, don’t forget you can fork the packed addons plugin sample on Github and use it as a starting point.