WordPress – Introduction to Plugin Development


Welcome to the Plugin Developer Handbook. Whether you’re writing your first plugin or your fiftieth, we hope this resource helps you write the best plugin possible.

The Plugin Developer Handbook covers a variety of topics — everything from what should be in the plugin header, to security best practices, to tools you can use to build your plugin. It’s also a work in progress — if you find something missing or incomplete, please edit and make it better.

There are three major components to WordPress:

  • core
  • themes
  • plugins

This handbook is about plugins and how they interact with WordPress. It will help you understand how they work and how to create your own.

Why We Make Plugins

If there’s one cardinal rule in WordPress development, it’s this: Don’t touch WordPress core. This means that you don’t edit core WordPress files to add functionality to your site. This is because, when WordPress updates to a new version, it overwrites all the core files. Any functionality you want to add should therefore be added through plugins using approved WordPress APIs.

WordPress plugins can be as simple or as complicated as you need them to be, depending on what you want to do. The simplest plugin is a single PHP file. The Hello Dolly plugin is an example of such a plugin. The plugin PHP file just needs a Plugin Header, a couple of PHP functions, and some hooks to attach your functions to.

Plugins allow you to greatly extend the functionality of WordPress without touching WordPress core itself.

What is a Plugin?

Plugins are packages of code that extend the core functionality of WordPress. WordPress plugins are made up of PHP code and other assets such as images, CSS, and JavaScript.

By making your own plugin you are extending WordPress, i.e. building additional functionality on top of what WordPress already offers. For example, you could write a plugin that displays links to the ten most recent posts on your site.

Or, using WordPress’ custom post types, you could write a plugin that creates a full-featured support ticketing system with email notifications, custom ticket statuses, and a client-facing portal. The possibilities are endless!

Most WordPress plugins are composed of many files, but a plugin really only needs one main file with a specifically formatted DocBlock in the header.

Hello Dolly, one of the first plugins, is only 82 lines long. Hello Dolly shows lyrics from the famous song in the WordPress admin. Some CSS is used in the PHP file to control how the lyric is styled.

As a WordPress.org plugin author, you have an amazing opportunity to create a plugin that will be installed, tinkered with, and loved by millions of WordPress users. All you need to do is turn your great idea into code. The Plugin Handbook is here to help you with that.

Plugin Basics

Getting Started #Getting Started

At its simplest, a WordPress plugin is a PHP file with a WordPress plugin header comment. It’s highly recommended that you create a directory to hold your plugin so that all of your plugin’s files are neatly organized in one place.

To get started creating a new plugin, follow the steps below.

  1. Navigate to your WordPress installation’s wp-content directory.
  2. Open the plugins directory.
  3. Create a new directory and name it after your plugin (e.g. plugin-name).
  4. Open your new plugin’s directory.
  5. Create a new PHP file (it’s also good to name this file after your plugin, e.g. plugin-name.php).

Here’s what the process looks like on the Unix command line:

1
2
3
4
5
wordpress$ cd wp-content
wp-content$ cd plugins
plugins$ mkdir plugin-name
plugins$ cd plugin-name
plugin-name$ vi plugin-name.php

In the example above, “vi” is the name of the text editor. Use whichever editor that is comfortable for you.

Now that you’re editing your new plugin’s PHP file, you’ll need to add a plugin header comment.  This is a specially formatted PHP block comment that contains metadata about your plugin, such as its name and author.  At the very least, the plugin header comment must contain the name of your plugin.  Only one file in your plugin’s folder should have the header comment—if your plugin has multiple PHP files, only one of those files should have the comment.

1
2
3
4
<?php
/*
Plugin Name: YOUR PLUGIN NAME
*/

After you save the file, you should be able to see your plugin listed in your WordPress site. Log in to your WordPress site, and click Plugins on the left navigation pane of your WordPress Admin. This page displays a listing of all the plugins your WordPress site has. Your new plugin should now be in that list!

Top ↑

Hooks: Actions and Filters #Hooks: Actions and Filters

WordPress hooks allow you to tap into WordPress at specific points to change how WordPress behaves without editing any core files.

There are two types of hooks within WordPress: actions and filters. Actions allow you to add or change WordPress functionality, while filters allow you to alter content as it is loaded and displayed to the website user.

Hooks are not just for plugin developers; hooks are used extensively to provide default functionality by WordPress core itself. Other hooks are unused place holders that are simply available for you to tap into when you need to alter how WordPress works. This is what makes WordPress so flexible.

Basic Hooks #Basic Hooks

The 3 basic hooks you’ll need when creating a plugin are the register_activation_hook(), register_deactivation_hook() and the register_uninstall_hook().

The activation hook is run when you activate your plugin. You would use this to provide a function to set up your plugin — for example, creating some default settings in the options table.

The deactivation hook is run when you deactivate your plugin. You would use this to provide a function that clears any temporary data stores by your plugin.

These uninstall methods are used to clean up after your plugin is deleted using the WordPress Admin. You would use this to delete all data created by your plugin, such as any options that were added to the options table.

Top ↑

Adding Hooks #Adding Hooks

You can add your own, custom hooks with do_action(), which will enable developers to extend your plugin by passing functions through your hooks.

Top ↑

Removing Hooks #Removing Hooks

You can also use invoke remove_action() to remove a function that was defined earlier. For example, if your plugin is an add-on to another plugin, you can use remove_action() with the same function callback that was added by the previous plugin with add_action(). The priority of actions is important in these situations, as remove_action() would need to run after the initial add_action().

You should be careful when removing an action from a hook, as well as when altering priorities, because it can be difficult to see how these changes will affect other interactions with the same hook. We highly recommend testing frequently.

You can learn more about creating hooks and interacting with them in the Hookssection of this handbook.

Top ↑

WordPress APIs #WordPress APIs

Did you know that WordPress provides a number of Application Programming Interfaces (APIs)? These APIs can greatly simplify the code you need to write in your plugins. You don’t want to reinvent the wheel, especially when so many people have done a lot of the work and testing for you.

The most common one is the Options API, which makes it easy to store data in the database for your plugin. If you’re thinking of using cURL in your plugin, the HTTP API might be of interest to you.

Since we’re talking about plugins, you’ll want to study the Plugin API. It has a variety of functions that will assist you in developing plugins.

Top ↑

How WordPress Loads Plugins #How WordPress Loads Plugins

When WordPress loads the list of installed plugins on the Plugins page of the WordPress Admin, it searches through the plugins folder (and its sub-folders) to find PHP files with WordPress plugin header comments. If your entire plugin consists of just a single PHP file, like Hello Dolly, the file could be located directly inside the root of the plugins folder. But more commonly, plugin files will reside in their own folder, named after the plugin.

Top ↑

Sharing your Plugin #Sharing your Plugin

Sometimes a plugin you create is just for your site. But many people like to share their plugins with the rest of the WordPress community. Before sharing your plugin, one thing you need to do is choose a license. This lets the user of your plugin know how they are allowed to use your code. To maintain compatibility with WordPress core, it is recommended that you pick a license that works with GNU General Public License (GPLv2+).

Header Requirements

As described in Getting Started, the header comment is what tells WordPress that a file is a plugin.

At a minimum, a header comment must contain the Plugin Name, but several pieces can – and usually should – be included:

  • Plugin Name: (required) The name of your plugin, which will be displayed in the Plugins list in the WordPress Admin.
  • Plugin URI: The home page of the plugin, which might be on WordPress.org or on your own website. This must be unique to your plugin.
  • Description: A short description of the plugin, as displayed in the Plugins section in the WordPress Admin. Keep this description to fewer than 140 characters.
  • Version: The current version number of the plugin, such as 1.0 or 1.0.3.

    Alert:When assigning a version number to your project, keep in mind that WordPress uses the PHP version_compare() function to compare plugin version numbers. Therefore, before you release a new version of your plugin, you should make sure that this PHP function considers the new version to be “greater” than the old one.  For example, 1.02 is actually greater than 1.1.

  • Author: The name of the plugin author. Multiple authors may be listed using commas.
  • Author URI: The author’s website or profile on another website, such as WordPress.org.
  • License: The short name (slug) of the plugin’s license (e.g. GPL2). More information about licensing can be found in the WordPress.org guidelines.
  • License URI: A link to the full text of the license (e.g. https://www.gnu.org/licenses/gpl-2.0.html).
  • Text Domain: The gettext text domain of the plugin. More information can be found in the Text Domain section of the How to Internationalize your Plugin page.
  • Domain Path: The domain path let WordPress know where to find the translations. More information can be found in the Domain Path section of the How to Internationalize your Plugin page.

A valid PHP file with a header comment might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/*
Plugin Name: WordPress.org Plugin
Description: Basic WordPress Plugin Header Comment
Version:     20160911
Author:      WordPress.org
License:     GPL2
Text Domain: wporg
Domain Path: /languages
*/

 

Including a Software License

Most WordPress plugins are released under the GPL, which is the same license that WordPress itself uses. However, there are other options available. It is always best to clearly indicate the license your plugin uses.

In the Header Requirements section, we briefly mentioned how you can indicate your plugin’s license within the plugin header comment. Another common, and encouraged, practice is to place a license block comment near the top of your main plugin file (the same one that has the plugin header comment).

This license block comment usually looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.
{Plugin Name} 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 {Plugin Name}. If not, see {URI to Plugin License}.
*/

When combined with the plugin header comment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
/*
Plugin Name: WordPress.org Plugin
Description: Basic WordPress Plugin Header Comment
Version:     20160911
Author:      WordPress.org
Text Domain: wporg
Domain Path: /languages
License:     GPL2
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.
{Plugin Name} 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 {Plugin Name}. If not, see {License URI}.
*/

Activation / Deactivation Hooks

Activation and deactivation hooks provide ways to perform actions when plugins are activated or deactivated.

On activation, plugins can run a routine to add rewrite rules, add custom database tables, or set default option values.

On deactivation, plugins can run a routine to remove temporary data such as cache and temp files and directories.

Alert:The deactivation hook is sometimes confused with the uninstall hook. The uninstall hook is best suited to delete all data permanently such as deleting plugin options and custom tables, etc.

Activation #Activation

To set up an activation hook, use the register_activation_hook() function:

1
register_activation_hook( __FILE__, 'pluginprefix_function_to_run' );

Top ↑

Deactivation #Deactivation

To set up a deactivation hook, use the register_deactivation_hook() function:

1
register_deactivation_hook( __FILE__, 'pluginprefix_function_to_run' );

The first parameter in each of these functions refers to your main plugin file, which is the file in which you have placed the plugin header comment. Usually these two functions will be triggered from within the main plugin file; however, if the functions are placed in any other file, you must update the first parameter to correctly point to the main plugin file.

Top ↑

Example #Example

One of the most common uses for an activation hook is to refresh WordPress permalinks when a plugin registers a custom post type. This gets rid of the nasty 404 errors.

Let’s look at an example of how to do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function pluginprefix_setup_post_types()
{
    // register the "book" custom post type
    register_post_type( 'book', ['public' => 'true'] );
}
add_action( 'init', 'pluginprefix_setup_post_type' );
function pluginprefix_install()
{
    // trigger our function that registers the custom post type
    pluginprefix_setup_post_type();
    // clear the permalinks after the post type has been registered
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'pluginprefix_install' );

If you are unfamiliar with registering custom post types, don’t worry – this will be covered later. This example is used simply because it’s very common.

Using the example from above, the following is how to reverse this process and deactivate a plugin:

1
2
3
4
5
6
7
8
function pluginprefix_deactivation()
{
    // our post type will be automatically removed, so no need to unregister it
    // clear the permalinks to remove our post type's rules
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'pluginprefix_deactivation' );

For further information regarding activation and deactivation hooks, here are some excellent resources:

Uninstall Methods

Your plugin may need to do some clean-up when it is uninstalled from a site.

A plugin is considered uninstalled if a user has deactivated the plugin, and then clicks the delete link within the WordPress Admin.

When your plugin is uninstalled, you’ll want to clear out any plugin options and/or settings specific to to the plugin, and/or other database entities such as tables.

Less experienced developers sometimes make the mistake of using the deactivation hook for this purpose.

This table illustrates the differences between deactivation and uninstall.

Scenario Deactivation Hook Uninstall Hook
Flush Cache/Temp Yes No
Flush Permalinks Yes No
Remove Options from {$wpdb->prefix}_options No Yes
Remove Tables from wpdb No Yes

Method 1: register_uninstall_hook #Method 1: register_uninstall_hook

To set up an uninstall hook, use the register_uninstall_hook() function:

1
register_uninstall_hook(__FILE__, 'pluginprefix_function_to_run');

Top ↑

Method 2: uninstall.php #Method 2: uninstall.php

To use this method you need to create an uninstall.php file inside the root folder of your plugin. This magic file is run automatically when the users deletes the plugin.

For example: /plugin-name/uninstall.php

Alert:When using uninstall.php, before executing, the plugin should always check for the constant WP_UNINSTALL_PLUGIN to prevent direct access.

The constant will be defined by WordPress during the uninstall.php invocation.

The constant is NOT defined when uninstall is performed by register_uninstall_hook().

Here is an example deleting option entries and dropping a database table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// if uninstall.php is not called by WordPress, die
if (!defined('WP_UNINSTALL_PLUGIN')) {
    die;
}
$option_name = 'wporg_option';
delete_option($option_name);
// for site options in Multisite
delete_site_option($option_name);
// drop a custom database table
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}mytable");

Note:In Multisite, looping through all blogs to delete options can be very resource intensive.

Best Practices

Here are some best practices to help organize your code so it works well alongside WordPress core and other WordPress plugins.

Avoid Naming Collisions #Avoid Naming Collisions

A naming collision happens when your plugin is using the same name for a variable, function or a class as another plugin.

Luckily, you can avoid naming collisions by using the methods below.

Procedural #Procedural

By default, all variables, functions and classes are defined in the global namespace, which means that it is possible for your plugin to override variables, functions and classes set by another plugin and vice-versa. Variables that are defined inside of functions or classes are not affected by this.

Prefix Everything #Prefix Everything

All variables, functions and classes should be prefixed with a unique identifier. Prefixes prevent other plugins from overwriting your variables and accidentally calling your functions and classes. It will also prevent you from doing the same.

Top ↑

Check for Existing Implementations #Check for Existing Implementations

PHP provides a number of functions to verify existence of variables, functions, classes and constants. All of these will return true if the entity exists.

Top ↑

Example #Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//Create a function called "wporg_init" if it doesn't already exist
if ( !function_exists( 'wporg_init' ) ) {
    function wporg_init() {
        register_setting( 'wporg_settings', 'wporg_option_foo' );
    }
}
//Create a function called "wporg_get_foo" if it doesn't already exist
if ( !function_exists( 'wporg_get_foo' ) ) {
    function wporg_get_foo() {
        return get_option( 'wporg_option_foo' );
    }
}

Top ↑

OOP #OOP

An easier way to tackle the naming collision problem is to use a class for the code of your plugin.

You will still need to take care of checking whether the name of the class you want is already taken but the rest will be taken care of by PHP.

Top ↑

Example #Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if ( !class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin
    {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }
        public static function get_foo() {
            return get_option( 'wporg_option_foo' );
        }
    }
    WPOrg_Plugin::init();
    WPOrg_Plugin::get_foo();
}

Top ↑

File Organization #File Organization

The root level of your plugin directory should contain your plugin-name.php file and, optionally, your uninstall.php file. All other files should be organized into sub folders whenever possible.

Top ↑

Folder Structure #Folder Structure

A clear folder structure helps you and others working on your plugin keep similar files together.

Here’s a sample folder structure for reference:

/plugin-name
     plugin-name.php
     uninstall.php
     /languages
     /includes
     /admin
          /js
          /css
          /images
     /public
          /js
          /css
          /images

Top ↑

Plugin Architecture #Plugin Architecture

The architecture, or code organization, you choose for your plugin will likely depend on the size of your plugin.

For small, single-purpose plugins that have limited interaction with WordPress core, themes or other plugins, there’s little benefit in engineering complex classes; unless you know the plugin is going to expand greatly later on.

For large plugins with lots of code, start off with classes in mind. Separate style and scripts files, and even build-related files. This will help code organization and long-term maintenance of the plugin.

Top ↑

Conditional Loading #Conditional Loading

It’s helpful to separate your admin code from the public code. Use the conditional is_admin().

For example:

1
2
3
4
5
<?php
if ( is_admin() ) {
    // we are in admin mode
    require_once( dirname( __FILE__ ) . '/admin/plugin-name-admin.php' );
}

Top ↑

Architecture Patterns #Architecture Patterns

While there are a number of possible architecture patterns, they can broadly be grouped into three variations:

Top ↑

Architecture Patterns Explained #Architecture Patterns Explained

Specific implementations of the more complex of the above code organizations have already been written up as tutorials and slides:

Top ↑

Boilerplate Starting Points #Boilerplate Starting Points

Instead of starting from scratch for each new plugin you write, you may want to start with a boilerplate. One advantage of using a boilerplate is to have consistency among your own plugins. Boilerplates also make it easier for other people to contribute to your code if you use a boilerplate they are already familiar with.

These also serve as further examples of different yet comparable architectures.

Of course, you could take different aspects of these and others to create your own custom boilerplate.

Plugin Security

Congratulations, your code works! But is it safe? How will the plugin protect your users if their site gets hacked? The best plugins in the WordPress.org directory keep their users’ information safe.

Please keep in mind that your code may be running across hundreds, perhaps even millions, of websites, so security is of the utmost importance.

In this chapter we will cover how to check user capabilities, validate and sanitize input, sanitize output and create and validate nonces.

Quick Reference #Quick Reference

See the complete example of security best practices for WordPress plugins and themes.

Top ↑

External Resources #External Resources

Checking User Capabilities

If your plugin allows users to submit data—be it on the Admin or the Public side—it should check for User Capabilities.

User Roles and Capabilities #User Roles and Capabilities

The most important step in creating an efficient security layer is having a user permission system in place. WordPress provides this in the form of User Roles and Capabilities.

Every user logged into WordPress is automatically assigned specific User capabilities depending on their User role.

User roles is just a fancy way of saying which group the user belongs to. Each group has a specific set of predefined capabilities.

For example, the main user of your website will have the User role of an Administrator while other users might have roles like Editor or Author. You could have more than one user assigned to a role, i.e. there might be two Administrators for a website.

User capabilities are the specific permissions that you assign to each user or to a User role.

For example, Administrators have the “manage_options” capability which allows them to view, edit and save options for the website. Editors on the other hand lack this capability which will prevent them from interacting with options.

These capabilities are then checked at various points within the Admin. Depending on the capabilities assigned to a role; menus, functionality, and other aspects of the WordPress experience may be added or removed.

As you build a plugin, make sure to run your code only when the current user has the necessary capabilities.

Hierarchy #Hierarchy

The higher the user role, the more capabilities the user has. Each user role inherits the previous roles in the hierarchy.

For example, the “Administrator”, which is the highest user role on a single site installation, inherits the following roles and their capabilities: “Subscriber”, “Contributor”, “Author” and “Editor”.

Top ↑

Examples #Examples

Top ↑

No Restrictions #No Restrictions

The example below creates a link on the frontend which gives the ability to trash posts. Because this code does not check user capabilities, it allows any visitor to the site to trash posts!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
/**
 * generate a Delete link based on the homepage url
 */
function wporg_generate_delete_link($content)
{
    // run only for single post page
    if (is_single() && in_the_loop() && is_main_query()) {
        // add query arguments: action, post
        $url = add_query_arg(
            [
                'action' => 'wporg_frontend_delete',
                'post'   => get_the_ID(),
            ],
            home_url()
        );
        return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
    }
    return null;
}
/**
 * request handler
 */
function wporg_delete_post()
{
    if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {
        // verify we have a post id
        $post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
        // verify there is a post with such a number
        $post = get_post((int)$post_id);
        if (empty($post)) {
            return;
        }
        // delete the post
        wp_trash_post($post_id);
        // redirect to admin page
        $redirect = admin_url('edit.php');
        wp_safe_redirect($redirect);
        // we are done
        die;
    }
}
/**
 * add the delete link to the end of the post content
 */
add_filter('the_content', 'wporg_generate_delete_link');
/**
 * register our request handler with the init hook
 */
add_action('init', 'wporg_delete_post');

Top ↑

Restricted to a Specific Capability #Restricted to a Specific Capability

The example above allows any visitor to the site to click on the “Delete” link and trash the post. However, we only want Editors and above to be able to click on the “Delete” link.

To accomplish this, we will check that the current user has the capability edit_others_posts, which only Editors or above would have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
/**
 * generate a Delete link based on the homepage url
 */
function wporg_generate_delete_link($content)
{
    // run only for single post page
    if (is_single() && in_the_loop() && is_main_query()) {
        // add query arguments: action, post
        $url = add_query_arg(
            [
                'action' => 'wporg_frontend_delete',
                'post'   => get_the_ID(),
            ],
            home_url()
        );
        return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
    }
    return null;
}
/**
 * request handler
 */
function wporg_delete_post()
{
    if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {
        // verify we have a post id
        $post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
        // verify there is a post with such a number
        $post = get_post((int)$post_id);
        if (empty($post)) {
            return;
        }
        // delete the post
        wp_trash_post($post_id);
        // redirect to admin page
        $redirect = admin_url('edit.php');
        wp_safe_redirect($redirect);
        // we are done
        die;
    }
}
if (current_user_can('edit_others_posts')) {
    /**
     * add the delete link to the end of the post content
     */
    add_filter('the_content', 'wporg_generate_delete_link');
    /**
     * register our request handler with the init hook
     */
    add_action('init', 'wporg_delete_post');
}

Data Validation

Data validation is the process of analyzing the data against a predefined pattern (or patterns) with a definitive result: valid or invalid.

Usually this applies to data coming from external sources such as user input and calls to web services via API.

Simple examples of data validation:

  • Check that required fields have not been left blank
  • Check that an entered phone number only contains numbers and punctuation
  • Check that an entered postal code is a valid postal code
  • Check that a quantity field is greater than 0

Data validation should be performed as early as possible. That means validating the data before performing any actions.

Note:Validation can be performed by using JavaScript on the front end and by using PHP on the back end.

Validating the Data #Validating the Data

There are at least three ways: built-in PHP functions, core WordPress functions, and custom functions you write.

Built-in PHP functions #Built-in PHP functions

Basic validation is doable using many built-in PHP functions, including these:

  • isset() and empty() for checking whether a variable exists and isn’t blank
  • mb_strlen() or strlen() for checking that a string has the expected number of characters
  • preg_match(), strpos() for checking for occurrences of certain strings in other strings
  • count() for checking how many items are in an array
  • in_array() for checking whether something exists in an array

Top ↑

Core WordPress functions #Core WordPress functions

WordPress provides many useful functions that help validate different kinds of data. Here are several examples:

  • is_email() will validate whether an email address is valid.
  • term_exists() checks whether a tag, category, or other taxonomy term exists.
  • username_exists() checks if username exists.
  • validate_file() will validate that an entered file path is a real path (but not whether the file exists).

Check the WordPress code reference for more functions like these.
Search for functions with names like these: *_exists(), *_validate(), and is_*(). Not all of these are validation functions, but many are helpful.

Top ↑

Custom PHP and JavaScript functions #Custom PHP and JavaScript functions

You can write your own PHP and JavaScript functions and include them in your plugin. When writing a validation function, you’ll want to name it like a question (examples: is_phone, is_available, is_us_zipcode).

The function should return a boolean, either true or false, depending on whether the data is valid or not. This will allow using the function as a condition.

Top ↑

Example 1 #Example 1

Let’s say you have an U.S. zip code input field that a user submits.

1
<input id="wporg_zip_code" type="text" maxlength="10" name="wporg_zip_code">

The text field allows up to 10 characters of input with no limitations on the types of characters that can be used. Users could enter something valid like 1234567890or something invalid (and evil) like eval().

The maxlength attribute on our input field is only enforced by the browser, so you still need to validate the length of the input on the server. If you don’t, an attacker could alter the maxlength value.

By using validation we can ensure we’re accepting only valid zip codes.

First you need to write a function to validate a U.S. zip codes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function is_us_zip_code($zip_code)
{
    // scenario 1: empty
    if (empty($zip_code)) {
        return false;
    }
    // scenario 2: more than 10 characters
    if (strlen(trim($zip_code)) > 10) {
        return false;
    }
    // scenario 3: incorrect format
    if (!preg_match('/^\d{5}(\-?\d{4})?$/', $zip_code)) {
        return false;
    }
    // passed successfully
    return true;
}

When processing the form, your code should check the wporg_zip_code field and perform the action based on the result:

1
2
3
if (isset($_POST['wporg_zip_code']) && is_us_zip_code($_POST['wporg_zip_code'])) {
    // your action
}

Top ↑

Example 2 #Example 2

Say you’re going to query the database for some posts, and you want to give the user the ability to sort the query results.

This example code checks an incoming sort key (stored in the “orderby” input parameter) for validity by comparing it against an array of allowed sort keys using the built-in PHP function in_array. This prevents the user from passing in malicious data and potentially compromising the website.

Before checking the incoming sort key against the array, the key is passed into the built-in WordPress function sanitize_key. This function ensures, among other things, that the key is in lowercase (in_array performs a case-sensitive search).

Passing “true” into the third parameter of in_array enables strict type checking, which tells the function to not only compare values but value types as well. This allows the code to be certain that the incoming sort key is a string and not some other data type.

1
2
3
4
5
6
7
8
<?php
$allowed_keys = ['author', 'post_author', 'date', 'post_date'];
$orderby = sanitize_key($_POST['orderby']);
if (in_array($orderby, $allowed_keys, true)) {
    // modify the query to sort by the orderby key
}

Securing Input

Securing input is the process of sanitizing (cleaning, filtering) input data.

You use sanitizing when you don’t know what to expect or you don’t want to be strict with data validation.

Any time you’re accepting potentially unsafe data, it is important to validate or sanitize it.

Sanitizing the Data #Sanitizing the Data

The easiest way to sanitize data is with built-in WordPress functions.

The sanitize_*() series of helper functions are super nice, as they ensure you’re ending up with safe data, and they require minimal effort on your part:

Top ↑

Example #Example

Let’s say we have an input field named title.

1
<input id="title" type="text" name="title">

You can sanitize the input data with the sanitize_text_field() function:

1
2
$title = sanitize_text_field($_POST['title']);
update_post_meta($post->ID, 'title', $title);

Behind the scenes, sanitize_text_field() does the following:

  • Checks for invalid UTF-8
  • Converts single less-than characters (<) to entity
  • Strips all tags
  • Removes line breaks, tabs and extra white space
  • Strips octets

Securing Output

Securing output is the process of escaping output data.

Escaping means stripping out unwanted data, like malformed HTML or script tags.

Whenever you’re rendering data, make sure to properly escape it. Escaping output prevents XSS (Cross-site scripting) attacks.

Note:Cross-site scripting (XSS) is a type of computer security vulnerability typically found in web applications. XSS enables attackers to inject client-side scripts into web pages viewed by other users. A cross-site scripting vulnerability may be used by attackers to bypass access controls such as the same-origin policy.

Escaping #Escaping

Escaping helps securing your data prior to rendering it for the end user. WordPress has a few helper functions you can use for most common scenarios.

  • esc_html() – Use this function anytime an HTML element encloses a section of data being displayed.
  • esc_url() – Use this function on all URLs, including those in the src and hrefattributes of an HTML element.
  • esc_js()– Use this function for inline Javascript.
  • esc_attr() – Use this function on everything else that’s printed into an HTML element’s attribute.

Alert:Most WordPress functions properly prepare data for output, so you don’t need to escape the data again. For example, you can safely call the_title() without escaping.

Top ↑

Escaping with Localization #Escaping with Localization

Rather than using echo to output data, it’s common to use the WordPress localization functions, such as _e() or __().

These functions simply wrap a localization function inside an escaping function:

1
2
3
esc_html_e( 'Hello World', 'text_domain' );
// same as
echo esc_html( __( 'Hello World', 'text_domain' ) );

These helper functions combine localization and escaping:

Top ↑

Custom Escaping #Custom Escaping

In the case that you need to escape your output in a specific way, the function wp_kses() (pronounced “kisses”) will come in handy.

This function makes sure that only the specified HTML elements, attributes, and attribute values will occur in your output, and normalizes HTML entities.

1
2
3
4
5
6
7
8
9
10
$allowed_html = [
    'a'      => [
        'href'  => [],
        'title' => [],
    ],
    'br'     => [],
    'em'     => [],
    'strong' => [],
];
echo wp_kses( $custom_content, $allowed_html );

wp_kses_post() is a wrapper function for wp_kses where $allowed_html is a set of rules used by post content.

1
echo wp_kses_post( $post_content );

Nonces

Nonces are generated numbers used to verify origin and intent of requests for security purposes. Each nonce can only be used once.

If your plugin allows users to submit data; be it on the Admin or the Public side; you have to make sure that the user is who they say they are and that they have the necessary capability to perform the action. Doing both in tandem means that data is only changing when the user expects it to be changing.

Using Nonces #Using Nonces

Following our checking user capabilities example, the next step in user data submission security is using nonces.

The capability check ensures that only users who have permission to delete a post are able to delete a post. But what if someone were to trick you into clicking that link? You have the necessary capability, so you could unwittingly delete a post.

Nonces can be used to check that the current user actually intends to perform the action.

When you generate the delete link, you’ll want to use wp_create_nonce() function to add a nonce to the link, the argument passed to the function ensures that the nonce being created is unique to that particular action.

Then, when you’re processing a request to delete a link, you can check that the nonce is what you expect it to be.

For more information, Mark Jaquith’s post on WordPress nonces is a great resource.

Top ↑

Complete Example #Complete Example

Complete example using capability checks, data validation, secure input, secure output and nonces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
/**
 * generate a Delete link based on the homepage url
 */
function wporg_generate_delete_link($content)
{
    // run only for single post page
    if (is_single() && in_the_loop() && is_main_query()) {
        // add query arguments: action, post, nonce
        $url = add_query_arg(
            [
                'action' => 'wporg_frontend_delete',
                'post'   => get_the_ID(),
                'nonce'  => wp_create_nonce('wporg_frontend_delete'),
            ],
            home_url()
        );
        return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
    }
    return null;
}
/**
 * request handler
 */
function wporg_delete_post()
{
    if (
        isset($_GET['action']) &&
        isset($_GET['nonce']) &&
        $_GET['action'] === 'wporg_frontend_delete' &&
        wp_verify_nonce($_GET['nonce'], 'wporg_frontend_delete')
    ) {
        // verify we have a post id
        $post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
        // verify there is a post with such a number
        $post = get_post((int)$post_id);
        if (empty($post)) {
            return;
        }
        // delete the post
        wp_trash_post($post_id);
        // redirect to admin page
        $redirect = admin_url('edit.php');
        wp_safe_redirect($redirect);
        // we are done
        die;
    }
}
if (current_user_can('edit_others_posts')) {
    /**
     * add the delete link to the end of the post content
     */
    add_filter('the_content', 'wporg_generate_delete_link');
    /**
     * register our request handler with the init hook
     */
    add_action('init', 'wporg_delete_post');
}

Hooks

Hooks are a way for one piece of code to interact/modify another piece of code. They make up the foundation for how plugins and themes interact with WordPress Core, but they’re also used extensively by Core itself.

There are two types of hooks: Actions and Filters. To use either, you need to write a custom function known as a Callback, and then register it with WordPress hook for a specific Action or Filter.

Actions allow you to add data or change how WordPress operates. Callback functions for Actions will run at a specific point in in the execution of WordPress, and can perform some kind of a task, like echoing output to the user or inserting something into the database.

Filters give you the ability to change data during the execution of WordPress. Callback functions for Filters will accept a variable, modify it, and return it. They are meant to work in an isolated manner, and should never have side effectssuch as affecting global variables and output.

WordPress provides many hooks that you can use, but you can also create your own so that other developers can extend and modify your plugin or theme.

External Resources

Actions

Actions are one of the two types of Hooks.

They provide a way for running a function at a specific point in the execution of WordPress Core, plugins, and themes. They are the counterpart to Filters.

Add Action #Add Action

The process of adding an action includes two steps.

First, you need to create a Callback function which will be called when the action is run. Second, you need to add your Callback function to a hook which will perform the calling of the function.

You will use the add_action() function, passing at least two parameters, string $tag, callable $function_to_add.

The example below will run when the init hook is executed:

1
2
3
4
5
6
<?php
function wporg_custom()
{
    // do something
}
add_action('init', 'wporg_custom');

You can refer to the Hooks chapter for a list of available hooks.

As you gain more experience, looking through WordPress Core source code will allow you to find the most appropriate hook.

Additional Parameters #Additional Parameters

add_action() can accept two additional parameters, int $priority for the priority given to the callback function, and int $accepted_args for the number of arguments that will be passed to the callback function.

Priority #Priority

The priority determines when the callback function will be executed in relation to the other callback functions associated with a given hook.

A function with a priority of 11 will run after a function with a priority of 10; and a function with a priority of 9 will run before a function with a priority of 10. Any positive integer is an acceptable value, and the default value is 10.

If two callback functions are registered for the same hook with the same priority, then will be run in the order that they were registered to the hook.

For example, the following callback functions are all registered to the
init hook, but with different priorities:

1
2
3
4
<?php
add_action('init', 'run_me_early', 9);
add_action('init', 'run_me_normal');    // default value of 10 is used since a priority wasn't specified
add_action('init', 'run_me_late', 11);

The first function to run will be run_me_early(), followed by run_me_normal(),and finally the last one to run will be run_me_late().

Top ↑

Number of Arguments #Number of Arguments

Sometimes it’s desirable for a callback function to receive some extra data related to the function that it’s hooking into.

For example, when WordPress saves a post and runs the save_post hook, it passes two parameters to the callback function: the ID of the post being saved, and the post object itself:

1
do_action('save_post', $post->ID, $post);

So, when a callback function is registered for the save_post hook, it can specify that it wants to receive those two arguments:

1
add_action('save_post', 'wporg_custom', 10, 2);

…and then it can register the arguments in the function definition:

1
2
3
4
function wporg_custom($post_id, $post)
{
    // do something
}

Top ↑

Example #Example

If you wanted to modify the query that fetches search results during The Loop on the frontend, you could hook into the pre_get_posts hook.

1
2
3
4
5
6
7
8
<?php
function wporg_search($query)
{
    if (!is_admin() && $query->is_main_query() && $query->is_search) {
        $query->set('post_type', ['post', 'movie']);
    }
}
add_action('pre_get_posts', 'wporg_search');

Filters

Filters are one of the two types of Hooks.

They provide a way for functions to modify data of other functions. They are the counterpart to Actions.

Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output.

Add Filter #Add Filter

The process of adding a filter includes two steps.

First, you need to create a Callback function which will be called when the filter is run. Second, you need to add your Callback function to a hook which will perform the calling of the function.

You will use the add_filter() function, passing at least two parameters, string $tag, callable $function_to_add.

The example below will run when the the_title filter is executed.

1
2
3
4
5
6
<?php
function wporg_filter_title($title)
{
    return 'The ' . $title . ' was filtered';
}
add_filter('the_title', 'wporg_filter_title');

Lets say we have a post title, “Learning WordPress”, the above example will modify it to be “The Learning WordPress was filtered”.

You can refer to the Hooks chapter for a list of available hooks.

As you gain more experience, looking through WordPress Core source code will allow you to find the most appropriate hook.

Additional Parameters #Additional Parameters

add_filter() can accept two additional parameters, int $priority for the priority given to the callback function, and int $accepted_args for the number of arguments that will be passed to the callback function.

For detailed explanation of these parameters please read the article on Actions.

Top ↑

Example #Example

To add a CSS class to the <body> tag when a certain condition is met:

1
2
3
4
5
6
7
8
9
<?php
function wporg_css_body_class($classes)
{
    if (!is_admin()) {
        $classes[] = 'wporg-is-awesome';
    }
    return $classes;
}
add_filter('body_class', 'wporg_css_body_class');

Custom Hooks

An important, but often overlooked practice is using custom hooks in your plugin so that other developers can extend and modify it.

Custom hooks are created and called in the same way that WordPress Core hooks are.

Create a Hook #Create a Hook

To create a custom hook, use do_action() for Actions and apply_filters() for Filters.

Note:We recommend using apply_filters() on any text that is output to the browser. Particularly on the frontend.

This makes it easier for plugins to be modified according to the user’s needs.

Top ↑

Add a Callback to the Hook #Add a Callback to the Hook

To add a callback function to a custom hook, use add_action() for Actions and add_filter() for Filters.

Top ↑

Naming Conflicts #Naming Conflicts

Since any plugin can create a custom hook, it’s important to prefix your hook names to avoid collisions with other plugins.

For example, a filter named email_body would be less useful because it’s likely that another developer will choose that same name. If the user installs both plugins, it could lead to bugs that are difficult to track down.

Naming the function wporg_email_body (where wporg_ is a unique prefix for your plugin) would avoid any collisions.

Top ↑

Examples #Examples

Extensible Action: Settings Form #Extensible Action: Settings Form

If your plugin adds a settings form to the Administrative Panels, you can use Actions to allow other plugins to add their own settings to it.

1
2
3
4
5
6
7
8
9
<?php
function wporg_settings_page_html()
{
    ?>
    Foo: <input id="foo" name="foo" type="text">
    Bar: <input id="bar" name="bar" type="text">
    <?php
    do_action('wporg_after_settings_page_html');
}

Now another plugin can register a callback function for the wporg_after_settings_page_html hook and inject new settings:

1
2
3
4
5
6
7
8
<?php
function myprefix_add_settings()
{
    ?>
    New 1: <input id="new_setting" name="new_settings" type="text">
    <?php
}
add_action('wporg_after_settings_page_html', 'myprefix_add_settings');

Top ↑

Extensible Filter: Custom Post Type #Extensible Filter: Custom Post Type

In this example, when the new post type is registered, the parameters that define it are passed through a filter, so another plugin can change them before the post type is created.

1
2
3
4
5
6
7
8
9
10
<?php
function wporg_create_post_type()
{
    $post_type_params = [/* ... */];
    register_post_type(
        'post_type_slug',
        apply_filters('wporg_post_type_params', $post_type_params)
    );
}

Now another plugin can register a callback function for the wporg_post_type_params hook and change post type parameters:

1
2
3
4
5
6
7
<?php
function myprefix_change_post_type_params($post_type_params)
{
    $post_type_params['hierarchical'] = true;
    return $post_type_params;
}
add_filter('wporg_post_type_params', 'myprefix_change_post_type_params');

Top ↑

External Resources #External Resources

Advanced Topics

Removing Actions and Filters #Removing Actions and Filters

Sometimes you want to remove a callback function from a hook that another plugin, theme or even WordPress Core has registered.

To remove a callback function from a hook, you need to call remove_action() or remove_filter(), depending whether the callback function was added as an Action or a Filter.

The parameters passed to remove_action() / remove_filter(), should be identical to the parameters passed to add_action() / add_filter()that registered it.

Alert:To successfully remove a callback function you must perform the removal after the callback function was registered. The order of execution is important.

Example #Example

Lets say we want to improve the performance of a large theme by removing unnecessary functionality.

Let’s analyze the theme’s code by looking into functions.php.

1
2
3
4
5
6
<?php
function my_theme_setup_slider()
{
    // ...
}
add_action('template_redirect', 'my_theme_setup_slider', 9);

The my_theme_setup_slider function is adding a slider that we don’t need, which probably loads a huge CSS file followed by a JavaScript initialization file which uses a custom written library the size of 1MB. We can can get rid of that.

Since we want to hook into WordPress after the my_theme_setup_slider callback function was registered (functions.php executed) our best chance would be the after_setup_theme hook.

1
2
3
4
5
6
7
8
<?php
function wporg_disable_slider()
{
    // make sure all parameters match the add_action() call exactly
    remove_action('template_redirect', 'my_theme_setup_slider', 9);
}
// make sure we call remove_action() after add_action() has been called
add_action('after_setup_theme', 'wporg_disable_slider');

Top ↑

Removing All Callbacks #Removing All Callbacks

You can also remove all of the callback functions associated with a hook by using remove_all_actions() / remove_all_filters().

Top ↑

Determining the Current Hook #Determining the Current Hook

Sometimes you want to run an Action or a Filter on multiple hooks, but behave differently based on which one is currently calling it.

You can use the current_action() / current_filter() to determine the current Action / Filter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function wporg_modify_content($content)
{
    switch (current_filter()) {
        case 'the_content':
            // do something
            break;
        case 'the_excerpt':
            // do something
            break;
    }
    return $content;
}
add_filter('the_content', 'wporg_modify_content');
add_filter('the_excerpt', 'wporg_modify_content');

Top ↑

Checking How Many Times a Hook Has Run #Checking How Many Times a Hook Has Run

Some hooks are called multiple times in the course of execution, but you may only want your callback function to run once.

In this situation, you can check how many times the hook has run with the did_action().

1
2
3
4
5
6
7
8
9
<?php
function wporg_custom()
{
    if (did_action('save_post') !== 1) {
        return;
    }
    // ...
}
add_action('save_post', 'wporg_custom');

Top ↑

Debugging with the “all” Hook #Debugging with the “all” Hook

If you want a callback function to fire on every single hook, you can register it to the all hook. Sometimes this is useful in debugging situations to help determine when a particular event is happening or when a page is crashing.

1
2
3
4
5
6
<?php
function wporg_debug()
{
    echo '<p>' . current_action() . '</p>';
}
add_action('all', 'wporg_debug');

Administration Menus

Administration Menus are the interfaces displayed in WordPress Administration. They allow you to add option pages for your plugin.

Note:For information on managing Navigation Menus, see the Navigation Menus chapter of the Theme Developer Handbook.

Top-Level Menus and Sub-Menus

The Top-level menus are rendered along the left side of the WordPress Administration. Each menu may contain a set of Sub-menus.

When deciding between Top-level menus and Sub-menus think carefully about the needs of your plugin as well as the needs of your end users.

Alert:We recommend developers with a single option page to add it as Sub-menu to one of the existing Top-level menus; such as Settings or Tools.

Top-Level Menus

Add a Top-Level Menu #Add a Top-Level Menu

To add a new Top-level menu to WordPress Administration, use the add_menu_page()function.

1
2
3
4
5
6
7
8
9
10
<?php
add_menu_page(
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = '',
    string $icon_url = '',
    int $position = null
);

Example #Example

Lets say we want to add a new Top-level menu called “WPOrg”.

The first step will be creating a function which will output the HTML. In this function we will perform the necessary security checks and render the options we’ve registered using the Settings API.

Note:We recommend wrapping your HTML using a

with a class of wrap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function wporg_options_page_html()
{
    // check user capabilities
    if (!current_user_can('manage_options')) {
        return;
    }
    ?>

    

class="wrap">

        

 

        "options.php" method="post">
            
            // output security fields for the registered setting "wporg_options"
            settings_fields('wporg_options');
            // output setting sections and their fields
            // (sections are registered for "wporg", each field is registered to a specific section)
            do_settings_sections('wporg');
            // output save settings button
            submit_button('Save Settings');
            ?>
        
    

 

    <?php
}

The second step will be registering our WPOrg menu. The registration needs to occur during the admin_menu action hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function wporg_options_page()
{
    add_menu_page(
        'WPOrg',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html',
        plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
        20
    );
}
add_action('admin_menu', 'wporg_options_page');

For a list of parameters and what each do please see the add_menu_page() in the reference.

Top ↑

Using a PHP File for HTML #Using a PHP File for HTML

The best practice for portable code would be to create a Callback that requires/includes your PHP file.

For the sake of completeness and helping you understand legacy code, we will show another way: passing a PHP file path as the $menu_slug parameter with an null $function parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function wporg_options_page()
{
    add_menu_page(
        'WPOrg',
        'WPOrg Options',
        'manage_options',
        plugin_dir_path(__FILE__) . 'admin/view.php',
        null,
        plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
        20
    );
}
add_action('admin_menu', 'wporg_options_page');

Top ↑

Remove a Top-Level Menu #Remove a Top-Level Menu

To remove a registered menu from WordPress Administration, use the remove_menu_page() function.

1
2
3
4
<?php
remove_menu_page(
    string $menu_slug
);

Warning:Removing menus won’t prevent users accessing them directly.
This should never be used as a way to restrict user capabilities.

Example #Example

Lets say we want to remove the “Tools” menu from.

1
2
3
4
5
6
<?php
function wporg_remove_options_page()
{
    remove_menu_page('tools.php');
}
add_action('admin_menu', 'wporg_remove_options_page', 99);

Make sure that the menu have been registered with the admin_menu hook before attempting to remove, specify a higher priority number for add_action().

Sub-Menus

Add a Sub-Menu #Add a Sub-Menu

To add a new Sub-menu to WordPress Administration, use the add_submenu_page()function.

1
2
3
4
5
6
7
8
9
<?php
add_submenu_page(
    string $parent_slug,
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = ''
);

Example #Example

Lets say we want to add a Sub-menu “WPOrg Options” to the “Tools” Top-level menu.

The first step will be creating a function which will output the HTML. In this function we will perform the necessary security checks and render the options we’ve registered using the Settings API.

Note:We recommend wrapping your HTML using a

with a class of wrap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function wporg_options_page_html()
{
    // check user capabilities
    if (!current_user_can('manage_options')) {
        return;
    }
    ?>

    

class="wrap">

        

 

        "options.php" method="post">
            
            // output security fields for the registered setting "wporg_options"
            settings_fields('wporg_options');
            // output setting sections and their fields
            // (sections are registered for "wporg", each field is registered to a specific section)
            do_settings_sections('wporg');
            // output save settings button
            submit_button('Save Settings');
            ?>
        
    

 

    <?php
}

The second step will be registering our WPOrg Options Sub-menu. The registration needs to occur during the admin_menu action hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function wporg_options_page()
{
    add_submenu_page(
        'tools.php',
        'WPOrg Options',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html'
    );
}
add_action('admin_menu', 'wporg_options_page');

For a list of parameters and what each do please see the add_submenu_page() in the reference.

Top ↑

Predefined Sub-Menus #Predefined Sub-Menus

Wouldn’t it be nice if we had helper functions that define the $parent_slug for WordPress built-in Top-level menus and save us from manually searching it through the source code?

Below is a list of parent slugs and their helper functions:

Top ↑

Remove a Sub-Menu #Remove a Sub-Menu

The process of removing Sub-menus is exactly the same as removing Top-level menus.

Shortcodes

As a security precaution, running PHP inside WordPress content is forbidden; to allow dynamic interactions with the content, Shortcodes were presented in WordPress version 2.5.

Shortcodes are macros that can be used to perform dynamic interactions with the content. i.e creating a gallery from images attached to the post or rendering a video.

Why Shortcodes? #Why Shortcodes?

Shortcodes are a valuable way of keeping content clean and semantic while allowing end users some ability to programmatically alter the presentation of their content.

When the end user adds a photo gallery to their post using a shortcode, they’re using the least data possible to indicate how the gallery should be presented.

Advantages:

  • No markup is added to the post content, which means that markup and styling can easily be manipulated on the fly or at a later state.
  • Shortcodes can also accept parameters, allowing users to modify how the shortcode behaves on an instance by instance basis.

Top ↑

Built-in Shortcodes #Built-in Shortcodes

By default, WordPress includes the following shortcodes:

  • – shortcode that allows you to wrap captions around content
  • – shortcode that allows you to show image galleries

  • – shortcode that allows you to embed and play audio files
  • – shortcode that allows you to embed and play video files
  • – shortcode that allows you to display collection of audio or video files
  • – shortcode that allows you to wrap embedded items

Top ↑

Shortcode Best Practices #Shortcode Best Practices

Best practices for developing shortcodes include the plugin development best practices and the list below:

  • Always return!
    Shortcodes are essentially filters, so creating “side effects” will lead to unexpected bugs.
  • Prefix your shortcode names to avoid collisions with other plugins.
  • Sanitize the input and escape the output.
  • Provide users with clear documentation on all shortcode attributes.

Top ↑

Quick Reference #Quick Reference

See the complete example of using a basic shortcode structure, taking care of self-closing and enclosing scenarios, shortcodes within shortcodes and securing output.

Top ↑

External Resources #External Resources

Basic Shortcodes

Add a Shortcode #Add a Shortcode

It is possible to add your own shortcodes by using the Shortcode API. The process involves registering a callback $func to a shortcode $tagusing add_shortcode().

1
2
3
4
5
<?php
add_shortcode(
    string $tag,
    callable $func
);

In a Theme #In a Theme

1
2
3
4
5
6
7
8
9
<?php
function wporg_shortcode($atts = [], $content = null)
{
    // do something to $content
    // always return
    return $content;
}
add_shortcode('wporg', 'wporg_shortcode');

[wporg] is your new shortcode. The use of the shortcode will trigger the wporg_shortcode callback function.

Top ↑

In a Plugin #In a Plugin

Unlike a Theme, a Plugin is run at a very early stage of the loading process thus requiring us to postpone the adding of our shortcode until WordPress has been initialized.

We recommend the init action hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function wporg_shortcodes_init()
{
    function wporg_shortcode($atts = [], $content = null)
    {
        // do something to $content
        // always return
        return $content;
    }
    add_shortcode('wporg', 'wporg_shortcode');
}
add_action('init', 'wporg_shortcodes_init');

Top ↑

Remove a Shortcode #Remove a Shortcode

It is possible to remove shortcodes by using the Shortcode API. The process involves removing a registered $tag using remove_shortcode().

1
2
3
4
<?php
remove_shortcode(
    string $tag
);

Make sure that the shortcode have been registered before attempting to remove. Specify a higher priority number for add_action() or hook into an action hook that is run later.

Top ↑

Check if a Shortcode Exists #Check if a Shortcode Exists

To check whether a shortcode has been registered use shortcode_exists().

Enclosing Shortcodes

The are two scenarios for using shortcodes:

  • The shortcode is a self-closing tag like we seen in the Basic Shortcodes section.
  • The shortcode is enclosing content.

Enclosing Content #Enclosing Content

Enclosing content with a shortcode allows manipulations on the enclosed content.

1
[wporg]content to manipulate[/wporg]

As seen above, all you need to do in order to enclose a section of content is add a beginning [$tag] and an end [/$tag], similar to HTML.

Top ↑

Processing Enclosed Content #Processing Enclosed Content

Lets get back to our original [wporg] shortcode code:

1
2
3
4
5
6
7
8
9
<?php
function wporg_shortcode($atts = [], $content = null)
{
    // do something to $content
    // always return
    return $content;
}
add_shortcode('wporg', 'wporg_shortcode');

Looking at the callback function we see that we chose to accept two parameters, $atts and $content. The $content parameter is going to hold our enclosed content. We will talk about $atts later.

The default value of $content is set to null so we can differentiate between a self-closing tag and enclosing tags by using PHP function is_null().

The shortcode [$tag], including its content and the end [/$tag] will be replaced with the return value of the handler function.

Alert:It is the responsibility of the handler function to secure the output.

Top ↑

Shortcode-ception #Shortcode-ception

The shortcode parser performs a single pass on the content of the post.

This means that if the $content parameter of a shortcode handler contains another shortcode, it won’t be parsed.

1
[wporg]another [shortcode] is included[/wporg]

Using shortcodes inside other shortcodes is possible by calling do_shortcode() on the final return value of the handler function.

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function wporg_shortcode($atts = [], $content = null)
{
    // do something to $content
    // run shortcode parser recursively
    $content = do_shortcode($content);
    // always return
    return $content;
}
add_shortcode('wporg', 'wporg_shortcode');

Top ↑

Limitations #Limitations

The shortcode parser is unable to handle mixing of enclosing and non-enclosing forms of the same [$tag].

1
[wporg] non-enclosed content [wporg]enclosed content[/wporg]

Instead of being treated as two shortcodes separated by the text “ non-enclosed content “, the parser treats this as a single shortcode enclosing “ non-enclosed content [wporg]enclosed content“.

Shortcodes with Parameters

Now that we know how to create a basic shortcode and how to use it as self-closing and enclosing, we will look at using parameters in shortcode [$tag] and handler function.

Shortcode [$tag] can accept parameters, known as attributes:

1
2
3
[wporg title="WordPress.org"]
Having fun with WordPress.org shortcodes.
[/wporg]

Shortcode handler function can accept 3 parameters:

  • $atts – array – [$tag] attributes
  • $content – string – post content
  • $tag – string – the name of the [$tag] (i.e. the name of the shortcode)
1
function wporg_shortcode($atts = [], $content = null, $tag = '') {}

Parsing Attributes #Parsing Attributes

For the user, shortcodes are just strings with square brackets inside the post content. The user have no idea which attributes are available and what happens behind the scenes.

For plugin developers, there is no way to enforce a policy on the use of attributes. The user may include one attribute, two or none at all.

To gain control of how the shortcodes are used:

Top ↑

Complete Example #Complete Example

Complete example using a basic shortcode structure, taking care of self-closing and enclosing scenarios, shortcodes within shortcodes and securing output.

A [wporg] shortcode that will accept a title and will display a box that we can style with CSS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
function wporg_shortcode($atts = [], $content = null, $tag = '')
{
    // normalize attribute keys, lowercase
    $atts = array_change_key_case((array)$atts, CASE_LOWER);
    // override default attributes with user attributes
    $wporg_atts = shortcode_atts([
                                     'title' => 'WordPress.org',
                                 ], $atts, $tag);
    // start output
    $o = '';
    // start box

    $o .= '

;
    // title

    $o .= '

. esc_html__($wporg_atts['title'], 'wporg') . '

;

    // enclosing tags
    if (!is_null($content)) {
        // secure output by executing the_content filter hook on $content
        $o .= apply_filters('the_content', $content);
        // run shortcode parser recursively
        $o .= do_shortcode($content);
    }
    // end box
    $o .= '

;

    // return output
    return $o;
}
function wporg_shortcodes_init()
{
    add_shortcode('wporg', 'wporg_shortcode');
}
add_action('init', 'wporg_shortcodes_init');

TinyMCE Enhanced Shortcodes

It’s possible to parse shortcodes within the visual editor of TinyMCE and make them render actual content, rather than the shortcode itself.

Switching to the Text tab allows you to see the actual shortcode again.

Below are the built-in WordPress shortcodes that use this functionality.

Audio Shortcode #Audio Shortcode

The shortcode allows you to embed a single audio file.

Top ↑

Caption Shortcode #Caption Shortcode

The caption shortcode wraps the image in a div and puts a <p class="wp-caption-text"> tag around the caption.

Top ↑

The

shortcode allows you to embed several images at once in a div.

Settings

WordPress provides two core APIs to make the administrative interfaces easy to build, secure, and consistent with the design of WordPress Administration.

The Settings API focuses on providing a way for developers to create forms and manage form data.

The Options API focuses on managing data using a simple key/value system.

Quick Reference

See the complete example of building a custom settings page using the Settings API and Options API.

Settings API

The Settings API, added in WordPress 2.7, allows admin pages containing settings forms to be managed semi-automatically. It lets you define settings pages, sections within those pages and fields within the sections.

New settings pages can be registered along with sections and fields inside them. Existing settings pages can also be added to by registering new settings sections or fields inside of them.

Organizing registration and validation of fields still requires some effort from developers, but avoids a lot of complex debugging of underlying options management.

Alert:When using the Settings API, the form POST to wp-admin/options.php which provides fairly strict capabilities checking. Users will need the manage_options capability (and in Multisite will have to be a Super Admin) to submit the form.

Why Use the Setting API? #Why Use the Setting API?

A developer could ignore this API and write their own settings page without it. That begs the question, what benefit does this API bring to the table? Following is a quick rundown of some of the benefits.

Visual Consistency #Visual Consistency

Using the API to generate your interface elements guarantees that your settings page will look like the rest of the administrative content. Have you ever seen a plugin settings page that looked like it was designed by a 5-year-old? You can bet that developer didn’t use the API. So, one strong argument is that your interface will look like it belongs, and thanks to the talented team of WordPress designers, it’ll look awesome!

Top ↑

Robustness (Future-Proofing!) #Robustness (Future-Proofing!)

Since the API is part of WordPress Core, any updates will automatically consider your plugin’s settings page. If you go off the reservation and make your own interface, WordPress Core updates are more likely to break your customizations. There is also a wider audience testing and maintaining that API code, so it will tend to be more stable.

Top ↑

Less Work! #Less Work!

Of course the most immediate benefit is that the WordPress API does a lot of work for you under the hood. Here are a few examples of things the Settings API does besides applying an awesome-looking, integrated design.

  • Handling Form Submissions – Let WordPress handle retrieving and storing your $_POST submissions.
  • Include Security Measures – You get extra security measures such as nonces, etc. for free.
  • Sanitizing Data – You get access to the same methods that the rest of WordPress uses for ensuring strings are safe to use.

Top ↑

Function Reference #Function Reference

Setting Register/Unregister Add Field/Section
register_setting()
unregister_setting()
add_settings_section()
add_settings_field()
Options Form Rendering Errors
settings_fields()
do_settings_sections()
do_settings_fields()
add_settings_error()
get_settings_errors()
settings_errors()

 

Top ↑

Playlist Shortcode #Playlist Shortcode

The shortcode allows you to attach more than one media file and render with an html5 playlist.

Top ↑

Video Shortcode #Video Shortcode

The shortcode is very similar to the shortcode, it simply renders a video instead of audio.

 

Using Settings API

Adding Settings #Adding Settings

You must define a new setting using register_setting(), it will create an entry in the {$wpdb->prefix}_options table.

You can add new sections on existing pages using add_settings_section().

You can add new fields to existing sections using add_settings_field().

Alert:register_setting() as well as the mentioned add_settings_*()functions should all be added to the admin_init action hook.

Add a Setting #Add a Setting

1
2
3
4
5
register_setting(
    string $option_group,
    string $option_name,
    callable $sanitize_callback = ''
);

Please refer to the Function Reference about register_setting() for full explanation about the used parameters.

Top ↑

Add a Section #Add a Section

1
2
3
4
5
6
add_settings_section(
    string $id,
    string $title,
    callable $callback,
    string $page
);

Sections are the groups of settings you see on WordPress settings pages with a shared heading. In your plugin you can add new sections to existing settings pages rather than creating a whole new page. This makes your plugin simpler to maintain and creates fewer new pages for users to learn.

Please refer to the Function Reference about add_settings_section() for full explanation about the used parameters.

Top ↑

Add a Field #Add a Field

1
2
3
4
5
6
7
8
add_settings_field(
    string $id,
    string $title,
    callable $callback,
    string $page,
    string $section = 'default',
    array $args = []
);

Please refer to the Function Reference about add_settings_field() for full explanation about the used parameters.

Top ↑

Example #Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
function wporg_settings_init()
{
    // register a new setting for "reading" page
    register_setting('reading', 'wporg_setting_name');
    // register a new section in the "reading" page
    add_settings_section(
        'wporg_settings_section',
        'WPOrg Settings Section',
        'wporg_settings_section_cb',
        'reading'
    );
    // register a new field in the "wporg_settings_section" section, inside the "reading" page
    add_settings_field(
        'wporg_settings_field',
        'WPOrg Setting',
        'wporg_settings_field_cb',
        'reading',
        'wporg_settings_section'
    );
}
/**
 * register wporg_settings_init to the admin_init action hook
 */
add_action('admin_init', 'wporg_settings_init');
/**
 * callback functions
 */
// section content cb
function wporg_settings_section_cb()
{
    echo '<p>WPOrg Section Introduction.</p>';
}
// field content cb
function wporg_settings_field_cb()
{
    // get the value of the setting we've registered with register_setting()
    $setting = get_option('wporg_setting_name');
    // output the field
    ?>
    <input type="text" name="wporg_setting_name" value="<?= isset($setting) ? esc_attr($setting) : ''; ?>">
    <?php
}

Top ↑

Getting Settings #Getting Settings

1
2
3
4
get_option(
    string $option,
    mixed $default = false
);

Getting settings is accomplished with the get_option() function.
The function accepts two parameters: the name of the option and an optional default value for that option.

Top ↑

Example #Example

1
2
// get the value of the setting we've registered with register_setting()
$setting = get_option('wporg_setting_name');

Options API

The Options API, added in WordPress 1.0, allows creating, reading, updating and deleting of WordPress options. In combination with the Settings API it allows controlling of options defined in settings pages.

Where Options are Stored? #Where Options are Stored?

Options are stored in the {$wpdb->prefix}_options table. $wpdb->prefix is defined by the $table_prefix variable set in the wp-config.php file.

Top ↑

How Options are Stored? #How Options are Stored?

Options may be stored in the WordPress database in one of two ways: as a single value or as an array of values.

Single Value #Single Value

When saved as a single value, the option name refers to a single value.

1
2
3
4
5
<?php
// add a new option
add_option('wporg_custom_option', 'hello world!');
// get an option
$option = get_option('wporg_custom_option');

Top ↑

Array of Values #Array of Values

When saved as an array of values, the option name refers to an array, which itself may be comprised key/value pairs.

1
2
3
4
5
6
7
8
9
<?php
// array of options
$data_r = ['title' => 'hello world!', 1, false];
// add a new option
add_option('wporg_custom_option', $data_r);
// get an option
$options_r = get_option('wporg_custom_option');
// output the title
echo esc_html($options_r['title']);

If you are working with a large number of related options, storing them as an array can have a positive impact on overall performance.

Note:Accessing data as individual options may result in many individual database transactions, and as a rule, database transactions are expensive operations (in terms of time and server resources). When you store or retrieve an array of options, it happens in a single transaction, which is ideal.

Top ↑

Function Reference #Function Reference

Add Option Get Option Update Option Delete Option
add_option() get_option() update_option() delete_option()
add_site_option() get_site_option() update_site_option() delete_site_option()

Options API

The Options API, added in WordPress 1.0, allows creating, reading, updating and deleting of WordPress options. In combination with the Settings API it allows controlling of options defined in settings pages.

Where Options are Stored? #Where Options are Stored?

Options are stored in the {$wpdb->prefix}_options table. $wpdb->prefix is defined by the $table_prefix variable set in the wp-config.php file.

Top ↑

How Options are Stored? #How Options are Stored?

Options may be stored in the WordPress database in one of two ways: as a single value or as an array of values.

Single Value #Single Value

When saved as a single value, the option name refers to a single value.

1
2
3
4
5
<?php
// add a new option
add_option('wporg_custom_option', 'hello world!');
// get an option
$option = get_option('wporg_custom_option');

Top ↑

Array of Values #Array of Values

When saved as an array of values, the option name refers to an array, which itself may be comprised key/value pairs.

1
2
3
4
5
6
7
8
9
<?php
// array of options
$data_r = ['title' => 'hello world!', 1, false];
// add a new option
add_option('wporg_custom_option', $data_r);
// get an option
$options_r = get_option('wporg_custom_option');
// output the title
echo esc_html($options_r['title']);

If you are working with a large number of related options, storing them as an array can have a positive impact on overall performance.

Note:Accessing data as individual options may result in many individual database transactions, and as a rule, database transactions are expensive operations (in terms of time and server resources). When you store or retrieve an array of options, it happens in a single transaction, which is ideal.

Top ↑

Function Reference #Function Reference

Add Option Get Option Update Option Delete Option
add_option() get_option() update_option() delete_option()
add_site_option() get_site_option() update_site_option() delete_site_option()

Custom Settings Page

Creating a custom settings page includes the combination of: creating an administration menu, using Settings API and Options API.

Alert:Please read these chapters before attempting to create your own settings page.

The example below can be used for quick reference on these topics by following the comments.

Complete Example

Complete example which adds a Top-Level Menu named WPOrg, registers a custom option named wporg_options and performs the CRUD (create, read, update, delete) logic using Settings API and Options API (including showing error/update messages).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?php
/**
 * @internal never define functions inside callbacks.
 * these functions could be run multiple times; this would result in a fatal error.
 */
/**
 * custom option and settings
 */
function wporg_settings_init() {
 // register a new setting for "wporg" page
 register_setting( 'wporg', 'wporg_options' );
 // register a new section in the "wporg" page
 add_settings_section(
 'wporg_section_developers',
 __( 'The Matrix has you.', 'wporg' ),
 'wporg_section_developers_cb',
 'wporg'
 );
 // register a new field in the "wporg_section_developers" section, inside the "wporg" page
 add_settings_field(
 'wporg_field_pill', // as of WP 4.6 this value is used only internally
 // use $args' label_for to populate the id inside the callback
 __( 'Pill', 'wporg' ),
 'wporg_field_pill_cb',
 'wporg',
 'wporg_section_developers',
 [
 'label_for' => 'wporg_field_pill',
 'class' => 'wporg_row',
 'wporg_custom_data' => 'custom',
 ]
 );
}
/**
 * register our wporg_settings_init to the admin_init action hook
 */
add_action( 'admin_init', 'wporg_settings_init' );
/**
 * custom option and settings:
 * callback functions
 */
// developers section cb
// section callbacks can accept an $args parameter, which is an array.
// $args have the following keys defined: title, id, callback.
// the values are defined at the add_settings_section() function.
function wporg_section_developers_cb( $args ) {
 ?>
 <p id="<?php echo esc_attr( $args['id'] ); ?>"><?php esc_html_e( 'Follow the white rabbit.', 'wporg' ); ?></p>
 <?php
}
// pill field cb
// field callbacks can accept an $args parameter, which is an array.
// $args is defined at the add_settings_field() function.
// wordpress has magic interaction with the following keys: label_for, class.
// the "label_for" key value is used for the "for" attribute of the <label>.
// the "class" key value is used for the "class" attribute of the <tr> containing the field.
// you can add custom key value pairs to be used inside your callbacks.
function wporg_field_pill_cb( $args ) {
 // get the value of the setting we've registered with register_setting()
 $options = get_option( 'wporg_options' );
 // output the field
 ?>
 <select id="<?php echo esc_attr( $args['label_for'] ); ?>"
 data-custom="<?php echo esc_attr( $args['wporg_custom_data'] ); ?>"
 name="wporg_options[<?php echo esc_attr( $args['label_for'] ); ?>]"
 >
 <option value="red" <?php echo isset( $options[ $args['label_for'] ] ) ? ( selected( $options[ $args['label_for'] ], 'red', false ) ) : ( '' ); ?>>
 <?php esc_html_e( 'red pill', 'wporg' ); ?>
 </option>
 <option value="blue" <?php echo isset( $options[ $args['label_for'] ] ) ? ( selected( $options[ $args['label_for'] ], 'blue', false ) ) : ( '' ); ?>>
 <?php esc_html_e( 'blue pill', 'wporg' ); ?>
 </option>
 </select>
 <p class="description">
 <?php esc_html_e( 'You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe.', 'wporg' ); ?>
 </p>
 <p class="description">
 <?php esc_html_e( 'You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes.', 'wporg' ); ?>
 </p>
 <?php
}
/**
 * top level menu
 */
function wporg_options_page() {
 // add top level menu page
 add_menu_page(
 'WPOrg',
 'WPOrg Options',
 'manage_options',
 'wporg',
 'wporg_options_page_html'
 );
}
/**
 * register our wporg_options_page to the admin_menu action hook
 */
add_action( 'admin_menu', 'wporg_options_page' );
/**
 * top level menu:
 * callback functions
 */
function wporg_options_page_html() {
 // check user capabilities
 if ( ! current_user_can( 'manage_options' ) ) {
 return;
 }
 // add error/update messages
 // check if the user have submitted the settings
 // wordpress will add the "settings-updated" $_GET parameter to the url
 if ( isset( $_GET['settings-updated'] ) ) {
 // add settings saved message with the class of "updated"
 add_settings_error( 'wporg_messages', 'wporg_message', __( 'Settings Saved', 'wporg' ), 'updated' );
 }
 // show error/update messages
 settings_errors( 'wporg_messages' );
 ?>

 

class="wrap">

 

echo esc_html( get_admin_page_title() ); ?>

 

 "options.php" method="post">
 
 // output security fields for the registered setting "wporg"
 settings_fields( 'wporg' );
 // output setting sections and their fields
 // (sections are registered for "wporg", each field is registered to a specific section)
 do_settings_sections( 'wporg' );
 // output save settings button
 submit_button( 'Save Settings' );
 ?>
 
 

 

 <?php
}

Metadata

Metadata, by its very definition, is information about information. In the case of WordPress, it’s information associated with posts, users, comments and terms.

An example would be a Content Type called Products with a metadata field for price. This field would be stored in the postmeta table.

Given the many-to-one relationship of metadata in WordPress, your options are fairly limitless. You can have as many meta options as you wish, and you can store just about anything in there.

This chapter will discuss managing post metadata, creating custom meta boxes, and rendering post metadata.

Managing Post Metadata

Adding Metadata #Adding Metadata

Adding metadata can be done quite easily with add_post_meta(). The function accepts a post_id, a meta_key, a meta_value, and a unique flag.

The meta_key is how your plugin will reference the meta value elsewhere in your code. Something like mycrazymetakeyname would work, however a prefix related to your plugin or theme followed by a description of the key would be more useful. wporg_featured_menu might be a good one. It should be noted that the same meta_key may be used multiple times to store variations of the metadata (see the unique flag below).

The meta_value can be a string, integer, or an array. If it’s an array, it will be automatically serialized before being stored in the database.

The unique flag allows you to declare whether this key should be unique. A non unique key is something a post can have multiple variations of, like price.
If you only ever want one price for a post, you should flag it unique and the meta_key will have one value only.

Top ↑

Updating Metadata #Updating Metadata

If a key already exists and you want to update it, use update_post_meta(). If you use this function and the key does NOT exist, then it will create it, as if you’d used add_post_meta().

Similar to add_post_meta(), the function accepts a post_id, a meta_key, a meta_value, and a unique flag.

Top ↑

Deleting Metadata #Deleting Metadata

delete_post_meta() takes a post_id, a meta_key, and optionally meta_value. It does exactly what the name suggests.

Top ↑

Character Escaping #Character Escaping

Post meta values are passed through the stripslashes() function upon being stored, so you will need to be careful when passing in values (such as JSON) that might include \ escaped characters.

Consider the JSON value {"key":"value with \"escaped quotes\""}:

1
2
3
4
5
6
7
$escaped_json = '{"key":"value with \"escaped quotes\""}';
update_post_meta($id, 'escaped_json', $escaped_json);
$broken = get_post_meta($id, 'escaped_json', true);
/*
$broken, after stripslashes(), ends up unparsable:
{"key":"value with "escaped quotes""}
*/

Workaround #Workaround

By adding one more level of \ escaping using the function wp_slash() (introduced in WP 3.6), you can compensate for the call to stripslashes():

1
2
3
4
5
6
7
$escaped_json = '{"key":"value with \"escaped quotes\""}';
update_post_meta($id, 'double_escaped_json', wp_slash($escaped_json));
$fixed = get_post_meta($id, 'double_escaped_json', true);
/*
$fixed, after stripslashes(), ends up as desired:
{"key":"value with \"escaped quotes\""}
*/

Top ↑

Hidden Custom Fields #Hidden Custom Fields

If you are a plugin or theme developer and you are planning to use custom fields to store parameters, it is important to note that WordPress will not show custom fields which have meta_key starting with an “_” (underscore) in the custom fields list on the post edit screen or when using the the_meta() template function.

This can be useful in order to show these custom fields in an unusual way by using the add_meta_box() function.

The example below will add a unique custom field with the meta_key name ‘_color’ and the meta_value of ‘red’ but this custom field will not display in the post edit screen:

1
add_post_meta(68, '_color', 'red', true);

Top ↑

Hidden Arrays #Hidden Arrays

In addition, if the meta_value is an array, it will not be displayed on the page edit screen, even if you don’t prefix the meta_key name with an underscore.

Custom Meta Boxes

What is a Meta Box? #What is a Meta Box?

When a user edits a post, the edit screen is composed of several default boxes: Editor, Publish, Categories, Tags, etc. These boxes are meta boxes. Plugins can add custom meta boxes to an edit screen of any post type.

The content of custom meta boxes are usually HTML form elements where the user enters data related to a Plugin’s purpose, but the content can be practically any HTML you desire.

Top ↑

Why Use Meta Boxes? #Why Use Meta Boxes?

Meta boxes are handy, flexible, modular edit screen elements that can be used to collect information related to the post being edited. Your custom meta box will be on the same screen as all the other post related information; so a clear relationship is established.

Meta boxes are easily hidden from users that do not need to see them, and displayed to those that do. Meta boxes can be user-arranged on the edit screen. The users are free to arrange the edit screen in a way that suits them, giving users control over their editing environment.

Alert:All examples on this page are for illustration purposes only. The code is not suitable for production environments.

Operations such as securing input, user capabilities, nonces, and internationalization have been intentionally omitted. Be sure to always address these important operations.

Top ↑

Adding Meta Boxes #Adding Meta Boxes

To create a meta box use the add_meta_box() function and plug it’s execution to the add_meta_boxes action hook.

The following example is adding a meta box to the post edit screen and the wporg_cpt edit screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
function wporg_add_custom_box()
{
    $screens = ['post', 'wporg_cpt'];
    foreach ($screens as $screen) {
        add_meta_box(
            'wporg_box_id',           // Unique ID
            'Custom Meta Box Title'// Box title
            'wporg_custom_box_html'// Content callback, must be of type callable
            $screen                   // Post type
        );
    }
}
add_action('add_meta_boxes', 'wporg_add_custom_box');

The wporg_custom_box_html function will hold the HTML for the meta box.

The following example is adding form elements, labels, and other HTML elements.

1
2
3
4
5
6
7
8
9
10
11
function wporg_custom_box_html($post)
{
    ?>
    <label for="wporg_field">Description for this field</label>
    <select name="wporg_field" id="wporg_field" class="postbox">
        <option value="">Select something...</option>
        <option value="something">Something</option>
        <option value="else">Else</option>
    </select>
    <?php
}

Note:Note there are no submit buttons in meta boxes. The meta box HTML is included inside the edit screen’s form tags, all the post data including meta box values are transfered via POST when the user clicks on the Publish or Update buttons.

The example shown here only contains one form field, a drop down list. You may create as many as needed in any particular meta box. If you have a lot of fields to display, consider using multiple meta boxes, grouping similar fields together in each meta box. This helps keep the page more organized and visually appealing.

Getting Values #Getting Values

To retrieve saved user data and make use of it, you need to get it from wherever you saved it initially. If it was stored in the postmeta table, you may get the data with get_post_meta().

The following example enhances the previous form elements with pre-populated data based on saved meta box values. You will learn how to save meta box values in the next section.

1
2
3
4
5
6
7
8
9
10
11
12
function wporg_custom_box_html($post)
{
    $value = get_post_meta($post->ID, '_wporg_meta_key', true);
    ?>
    <label for="wporg_field">Description for this field</label>
    <select name="wporg_field" id="wporg_field" class="postbox">
        <option value="">Select something...</option>
        <option value="something" <?php selected($value, 'something'); ?>>Something</option>
        <option value="else" <?php selected($value, 'else'); ?>>Else</option>
    </select>
    <?php
}

More on the selected() function.

Top ↑

Saving Values #Saving Values

When a post type is saved or updated, several actions fire, any of which might be appropriate to hook into in order to save the entered values. In this example we use the save_post action hook but other hooks may be more appropriate for certain situations. Be aware that save_post may fire more than once for a single update event. Structure your approach to saving data accordingly.

You may save the entered data anywhere you want, even outside WordPress. Since you are probably dealing with data related to the post, the postmeta table is often a good place to store data.

The following example will save the wporg_field field value in the _wporg_meta_key meta key, which is hidden.

1
2
3
4
5
6
7
8
9
10
11
function wporg_save_postdata($post_id)
{
    if (array_key_exists('wporg_field', $_POST)) {
        update_post_meta(
            $post_id,
            '_wporg_meta_key',
            $_POST['wporg_field']
        );
    }
}
add_action('save_post', 'wporg_save_postdata');

In production code, remember to follow the security measures outlined in the info box!

Top ↑

Behind the Scenes #Behind the Scenes

You don’t normally need to be concerned about what happens behind the scenes. This section was added for completeness.

When a post edit screen wants to display all the meta boxes that were added to it, it calls the do_meta_boxes() function. This function loops through all meta boxes and invokes the callback associated with each.
In between each call, intervening markup (such as divs, titles, etc.) is added.

Top ↑

Removing Meta Boxes #Removing Meta Boxes

To remove an existing meta box from an edit screen use the remove_meta_box()function. The passed parameters must exactly match those used to add the meta box with add_meta_box().

To remove default meta boxes check the source code for the parameters used. The default add_meta_box() calls are made from wp-includes/edit-form-advanced.php.

Top ↑

Implementation Variants #Implementation Variants

So far we’ve been using the procedural technique of implementing meta boxes. Many plugin developers find the need to implement meta boxes using various other techniques.

Top ↑

OOP #OOP

Adding meta boxes using OOP is easy and saves you from having to worry about naming collisions in the global namespace.
To save memory and allow easier implementation, the following example uses an abstract Class with static methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
abstract class WPOrg_Meta_Box
{
    public static function add()
    {
        $screens = ['post', 'wporg_cpt'];
        foreach ($screens as $screen) {
            add_meta_box(
                'wporg_box_id',          // Unique ID
                'Custom Meta Box Title', // Box title
                [self::class, 'html'],   // Content callback, must be of type callable
                $screen                  // Post type
            );
        }
    }
    public static function save($post_id)
    {
        if (array_key_exists('wporg_field', $_POST)) {
            update_post_meta(
                $post_id,
                '_wporg_meta_key',
                $_POST['wporg_field']
            );
        }
    }
    public static function html($post)
    {
        $value = get_post_meta($post->ID, '_wporg_meta_key', true);
        ?>
        <label for="wporg_field">Description for this field</label>
        <select name="wporg_field" id="wporg_field" class="postbox">
            <option value="">Select something...</option>
            <option value="something" <?php selected($value, 'something'); ?>>Something</option>
            <option value="else" <?php selected($value, 'else'); ?>>Else</option>
        </select>
        <?php
    }
}
add_action('add_meta_boxes', ['WPOrg_Meta_Box', 'add']);
add_action('save_post', ['WPOrg_Meta_Box', 'save']);

Top ↑

AJAX #AJAX

Since the HTML elements of the meta box are inside the form tags of the edit screen, the default behavior is to parse meta box values from the $_POST super global after the user have submitted the page.

You can enhance the default experience with AJAX; this allows to perform actions based on user input and behavior; regardless if they’ve submitted the page.

Define a Trigger #Define a Trigger

First, you must define the trigger, this can be a link click, a change of a value or any other JavaScript event.

In the example below we will define change as our trigger for performing an AJAX request.

1
2
3
4
5
6
7
8
9
10
11
/*jslint browser: true, plusplus: true */
(function ($, window, document) {
    'use strict';
    // execute when the DOM is ready
    $(document).ready(function () {
        // js 'change' event triggered on the wporg_field form field
        $('#wporg_field').on('change', function () {
            // our code
        });
    });
}(jQuery, window, document));

Top ↑

Client Side Code #Client Side Code

Next, we need to define what we want the trigger to do, in other words we need to write our client side code.

In the example below we will make a POST request, the response will either be success or failure, this will indicate wither the value of the wporg_field is valid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*jslint browser: true, plusplus: true */
(function ($, window, document) {
    'use strict';
    // execute when the DOM is ready
    $(document).ready(function () {
        // js 'change' event triggered on the wporg_field form field
        $('#wporg_field').on('change', function () {
            // jQuery post method, a shorthand for $.ajax with POST
            $.post(wporg_meta_box_obj.url,                        // or ajaxurl
                   {
                       action: 'wporg_ajax_change',               // POST data, action
                       wporg_field_value: $('#wporg_field').val() // POST data, wporg_field_value
                   }, function (data) {
                        // handle response data
                        if (data === 'success') {
                            // perform our success logic
                        } else if (data === 'failure') {
                            // perform our failure logic
                        } else {
                            // do nothing
                        }
                    }
            );
        });
    });
}(jQuery, window, document));

We took the WordPress AJAX file URL dynamically from the wporg_meta_box_objJavaScript custom object that we will create in the next step.

Note:If your meta box only requires the WordPress AJAX file URL; instead of creating a new custom JavaScript object you could use the ajaxurl predefined JavaScript variable.
Available only in the WordPress Administration. Make sure it is not empty before performing any logic.

Top ↑

Enqueue Client Side Code #Enqueue Client Side Code

Next step is to put our code into a script file and enqueue it on our edit screens.

In the example below we will add the AJAX functionality to the edit screens of the following post types: post, wporg_cpt.

The script file will reside at /plugin-name/admin/meta-boxes/js/admin.js,
plugin-name being the main plugin folder,
/plugin-name/plugin.php the file calling the function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function wporg_meta_box_scripts()
{
    // get current admin screen, or null
    $screen = get_current_screen();
    // verify admin screen object
    if (is_object($screen)) {
        // enqueue only for specific post types
        if (in_array($screen->post_type, ['post', 'wporg_cpt'])) {
            // enqueue script
            wp_enqueue_script('wporg_meta_box_script', plugin_dir_url(__FILE__) . 'admin/meta-boxes/js/admin.js', ['jquery']);
            // localize script, create a custom js object
            wp_localize_script(
                'wporg_meta_box_script',
                'wporg_meta_box_obj',
                [
                    'url' => admin_url('admin-ajax.php'),
                ]
            );
        }
    }
}
add_action('admin_enqueue_scripts', 'wporg_meta_box_scripts');

Top ↑

Server Side Code #Server Side Code

The last step is to write our server side code that is going to handle the request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function wporg_meta_box_ajax_handler()
{
    if (isset($_POST['wporg_field_value'])) {
        switch ($_POST['wporg_field_value']) {
            case 'something':
                echo 'success';
                break;
            default:
                echo 'failure';
                break;
        }
    }
    // ajax handlers must die
    die;
}
// wp_ajax_ is the prefix, wporg_ajax_change is the action we've used in client side code
add_action('wp_ajax_wporg_ajax_change', 'wporg_meta_box_ajax_handler');

As a final reminder, the code illustrated on this page lacks important operations that take care of security. Be sure your production code includes such operations.

See Handbook’s AJAX Chapter and the Codex for more on AJAX.

Top ↑

More Information #More Information

Rendering Post Metadata

There are two functions that allow easy access to metadata stored in the postmeta table: get_post_meta(), get_post_custom().

Please see the function reference on parameter details.

1
2
3
4
5
get_post_meta(
    int $post_id,
    string $key = '',
    bool $single = false
);

Example:

1
$wporg_meta_value = get_post_meta(get_the_ID(), 'wporg_meta_key');

Please see the function reference on parameter details.

1
2
3
get_post_custom(
    int $post_id
);

Example:

1
$meta_array = get_post_custom(get_the_ID());

Custom Post Types

WordPress stores the Post Types in the posts table allowing developers to register Custom Post Types along the ones that already exist.

This chapter will show you how to register Custom Post Types, how to retrieve their content from the database, and how to render them to the public.

Registering Custom Post Types

WordPress comes with five default post types: post, page, attachment, revision, menu.

While developing your plugin, you may need to create your own specific content type: for example, products for an e-commerce website, assignments for an e-learning website, or movies for a review website.

Using Custom Post Types, you can register your own post type. Once a post type is registered, it gets a new top-level administrative screen that can be used to manage and create posts of that type.

To register a new post type, you use the register_post_type() function.

Alert:We recommend that you put custom post types in a plugin rather than a theme. This ensures that user content remains portable even if they change their theme.

The following example registers a new post type, Products, which is identified in the database as wporg_product.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function wporg_custom_post_type()
{
    register_post_type('wporg_product',
                       [
                           'labels'      => [
                               'name'          => __('Products'),
                               'singular_name' => __('Product'),
                           ],
                           'public'      => true,
                           'has_archive' => true,
                       ]
    );
}
add_action('init', 'wporg_custom_post_type');

Please visit the reference page of register_post_type() for the description of arguments.

Warning:You must call register_post_type() before the admin_init and after the after_setup_theme action hooks. A good hook to use is the init action hook.

Naming Best Practices #Naming Best Practices

It is important that you prefix your post type functions and identifiers with a short prefix that corresponds to your plugin, theme, or website.

Warning:To ensure forward compatibility, do not use wp_ as your identifier — it is being used by WordPress core.

Make sure your custom post type identifier does not exceed 20 characters as the post_type column in the database is currently a VARCHAR field of that length.

If your identifier is too generic — for example: product. It may conflict with other plugins or themes.

Top ↑

URLs #URLs

A custom post type gets its own slugs within the site URL structure.

A post of type wporg_product will use the following URL structure: http://example.com/wporg_product/%product_name%.

wporg_product is the slug of your custom post type and %product_name% is the slug of your particular product.

The final permalink would be: http://example.com/wporg_product/wporg-is-awesome.

You can see the permalink on the edit screen for your custom post type, just like with default post types.

A Custom Slug for a Custom Post Type #A Custom Slug for a Custom Post Type

To set a custom slug for the slug of your custom post type all you need to do is add a key => value pair to the rewrite key in the register_post_type() arguments array.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function wporg_custom_post_type()
{
    register_post_type('wporg_product',
                       [
                           'labels'      => [
                               'name'          => __('Products'),
                               'singular_name' => __('Product'),
                           ],
                           'public'      => true,
                           'has_archive' => true,
                           'rewrite'     => ['slug' => 'products'], // my custom slug
                       ]
    );
}
add_action('init', 'wporg_custom_post_type');

The above will result in the following URL structure: http://example.com/products/%product_name%

Warning:Using a generic slug like products can potentially conflict with other plugins or themes.

Note:Unlike the custom post type identifiers, the duplicate slug problem can be solved easily by changing the slug for one of the conflicting post types.

If the plugin author was smart enough to include an apply_filters()call on the arguments, this can be done programmatically by overriding the arguments submitted via the register_post_type()function.

Solving duplicate post type identifiers is not possible without disabling one of the conflicting post types.

Working with Custom Post Types

Custom Post Type Templates #Custom Post Type Templates

You can create custom templates for your custom post types. In the same way posts and their archives can be displayed using single.php and archive.php, you can create the templates:

  • single-{post_type}.php – for single posts of a custom post type
  • archive-{post_type}.php – for the archive

Where {post_type} is the $post_type argument of the register_post_type()function.

Building upon what we’ve learned previously, you could create single-wporg_product.php and archive-wporg_product.php template files for single product posts and the archive.

Alternatively, you can use the is_post_type_archive() function in any template file to check if the query shows an archive page of a given post type, and the post_type_archive_title() function to display the post type title.

Top ↑

Querying by Post Type #Querying by Post Type

You can query posts of a specific type by passing the post_type key in the arguments array of the WP_Query class constructor.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$args = [
    'post_type'      => 'product',
    'posts_per_page' => 10,
];
$loop = new WP_Query($args);
while ($loop->have_posts()) {
    $loop->the_post();
    ?>

    

class="entry-content">
        
        
    

 

    <?php
}

This loops through the latest ten product posts and displays the title and content of them one by one.

Top ↑

Altering the Main Query #Altering the Main Query

Registering a custom post type does not mean it gets added to the main query automatically.

If you want your custom post type posts to show up on standard archives or include them on your home page mixed up with other post types, use the pre_get_posts action hook.

The next example will show posts from post, page and movie post types on the home page:

1
2
3
4
5
6
7
8
functionwporg_add_custom_post_types($query)
{
    if(is_home() && $query->is_main_query()) {
        $query->set('post_type', ['post', 'page', 'movie']);
    }
    return$query;
}
add_action('pre_get_posts', 'wporg_add_custom_post_types');

Taxonomies

A Taxonomy is a fancy word for classifying/grouping of things. Taxonomies can be hierarchical (with parents/children) or flat.

WordPress stores the Taxonomies in the term_taxonomy table allowing developers to register Custom Taxonomies along the ones that already exist.

Taxonomies have Terms which serve as the topic by which you classify/group things. They are stored inside the terms table.

E.g. a Taxonomy named “Art” will have multiple Terms; they could be “Modern” and “18th Century”.

This chapter will show you how to register Custom Taxonomies, how to retrieve their content from the database, and how to render them to the public.

Note:WordPress 3.4 and earlier had a Taxonomy named “Links” which was deprecated in WordPress 3.5.

Working with Custom Taxonomies

Introduction to Taxonomies #Introduction to Taxonomies

To understand what Taxonomies are and what they do please read the Taxonomy introduction.

Top ↑

Custom Taxonomies #Custom Taxonomies

As classification systems go, “Categories” and “Tags” aren’t very structured, so it may be beneficial for a developer to create their own.

WordPress allows developers to create Custom Taxonomies. Custom Taxonomies are useful when one wants to create distinct naming systems and make them accessible behind the scenes in a predictable way.

Top ↑

Why Use Custom Taxonomies? #Why Use Custom Taxonomies?

You might ask, “Why bother creating a Custom Taxonomy, when I can organize by Categories and Tags?”

Well… let’s use an example. Suppose we have a client that is a chef who wants you to create a website where she’ll feature original recipes.

One way to organize the website might be to create a Custom Post Type called “Recipes” to store her recipes. Then create a Taxonomy “Courses” to separate “Appetizers” from “Desserts”, and finally a Taxonomy “Ingredients” to separate “Chicken” from “Chocolate” dishes.

These groups could be defined using Categories or Tags, you could have a “Courses” Category with Subcategories for “Appetizers” and “Desserts”, and an “Ingredients” Category with Subcategories for each ingredient.

The main advantage of using Custom Taxonomies is that you can reference “Courses” and “Ingredients” independently of Categories and Tags. They even get their own menus in the Administration area.

In addition, creating Custom Taxonomies allows you to build custom interfaces which will ease the life of your client and make the process of inserting data intuitive to their business nature.

Now imagine that these Custom Taxonomies and the interface is implemented inside a plugin; you’ve just built your own Recipes plugin that can be reused on any WordPress website.

Top ↑

Example: Courses Taxonomy #Example: Courses Taxonomy

The following example will show you how to create a plugin which adds a Custom Taxonomy “Courses” to the default post Post Type.

Please make sure to read the Plugin Basics chapter before attempting to create your own plugin.

Step 1: Before You Begin #Step 1: Before You Begin

Go to Posts > Add New page. You will notice that you only have Categories and Tags.

No Custom Taxonomy Meta Box (Yet)

Top ↑

Step 2: Creating a New Plugin #Step 2: Creating a New Plugin

Register the Taxonomy “course” for the post type “post” using the init action hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
/*
* Plugin Name: Course Taxonomy
* Description: A short example showing how to add a taxonomy called Course.
* Version: 1.0
* Author: developer.wordpress.org
*/
functionwporg_register_taxonomy_course()
{
    $labels= [
        'name'=> _x('Courses', 'taxonomy general name'),
'singular_name'=> _x('Course', 'taxonomy singular name'),
'search_items'=> __('Search Courses'),
'all_items'=> __('All Courses'),
'parent_item'=> __('Parent Course'),
'parent_item_colon'=> __('Parent Course:'),
'edit_item'=> __('Edit Course'),
'update_item'=> __('Update Course'),
'add_new_item'=> __('Add New Course'),
'new_item_name'=> __('New Course Name'),
'menu_name'=> __('Course'),
];
$args= [
'hierarchical'=> true, // make it hierarchical (like categories)
'labels'=> $labels,
'show_ui'=> true,
'show_admin_column'=> true,
'query_var'=> true,
'rewrite'=> ['slug'=> 'course'],
];
register_taxonomy('course', ['post'], $args);
}
add_action('init', 'wporg_register_taxonomy_course');

Top ↑

Step 3: Review the Result #Step 3: Review the Result

Activate your plugin, then go to Posts > Add New. You should see a new meta box for your “Courses” Taxonomy.

courses_taxonomy_post_screen

Top ↑

Code Breakdown #Code Breakdown

The following discussion breaks down the code used above describing the functions and parameters.

The function wporg_register_taxonomy_course contains all the steps necessary for registering a Custom Taxonomy.

The $labels array holds the labels for the Custom Taxonomy.
These labels will be used for displaying various information about the Taxonomy in the Administration area.

The $args array holds the configuration options that will be used when creating our Custom Taxonomy.

The function register_taxonomy() creates a new Taxonomy with the identifier course for the post Post Type using the $args array for configuration.

The function add_action() ties the wporg_register_taxonomy_course function execution to the init action hook.

$args #$args

The $args array holds important configuration for the Custom Taxonomy, it instructs WordPress how the Taxonomy should work.

Take a look at register_taxonomy() function for a full list of accepted parameters and what each of these do.

Top ↑

Summary #Summary

With our Courses Taxonomy example, WordPress will automatically create an archive page and child pages for the course Taxonomy.

The archive page will be at /course/ with child pages spawning under it using the Term’s slug (/course/%%term-slug%%/).

Top ↑

Using Your Taxonomy #Using Your Taxonomy

WordPress has many functions for interacting with your Custom Taxonomy and the Terms within it.

Here are some examples:

  • the_terms: Takes a Taxonomy argument and renders the terms in a list.
  • wp_tag_cloud: Takes a Taxonomy argument and renders a tag cloud of the terms.
  • is_taxonomy: Allows you to determine if a given taxonomy exists.

https://developer.wordpress.org/reference/functions/wp_get_split_term/

Users

WordPress stores the Users in the users table.

What is a user? #What is a user?

Each WordPress user has, at the bare minimum, a username, password and email address.

Once a user account is created, that user may log in using the WordPress Admin (or programmatically) to access WordPress functions and data.

Top ↑

Roles and Capabilities #Roles and Capabilities

Users are assigned roles, and each role has a set of capabilities.

You can create new roles with their own set of capabilities.

Custom capabilities can also be created and assigned to existing roles or new roles.

In WordPress, developers can take advantage of user roles to limit the set of actions an account can perform.

Top ↑

The Principle of Least Privileges #The Principle of Least Privileges

WordPress adheres to the principal of least privileges, the practice of giving a user only the privileges that are essential for performing the desired work.

Working with Users

Adding Users #Adding Users

To add a user you can use wp_create_user() or wp_insert_user().

wp_create_user() creates a user using only the username, password and email parameters while while wp_insert_user() accepts an array or object describing the user and it’s properties.

Create User #Create User

1
2
3
4
5
wp_create_user(
    string $username,
    string $password,
    string $email = ''
);

Allows you to create a new WordPress user.

Note:It uses wp_slash() to escape the values. The PHP compact() function to create an array with these values. The wp_insert_user()to perform the insert operation.

Please refer to the Function Reference about wp_create_user() for full explanation about the used parameters.

Example Create #Example Create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// check if the username is taken
$user_id = username_exists($user_name);
// check that the email address does not belong to a registered user
if (!$user_id && email_exists($user_email) === false) {
// create a random password
    $random_password = wp_generate_password(
        $length = 12,
        $include_standard_special_chars = false
    );
// create the user
    $user_id = wp_create_user(
        $user_name,
        $random_password,
        $user_email
    );
}

Top ↑

Insert User #Insert User

1
2
3
wp_insert_user(
    array|object|WP_User $userdata
);

Note:The function calls a filter for most predefined properties.

The function performs the action user_register when creating a user (user ID does not exist).

The function performs the action profile_update when updating the user (user ID exists).

Please refer to the Function Reference about wp_insert_user() for full explanation about the used parameters.

Top ↑

Example Insert #Example Insert

Below is an example showing how to insert a new user with the website profile field filled in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$username = $_POST['username'];
$password = $_POST['password'];
$website = $_POST['website'];
$user_data = [
    'user_login' => $username,
    'user_pass'  => $password,
    'user_url'   => $website,
];
$user_id = wp_insert_user($user_data);
// success
if (!is_wp_error($user_id)) {
    echo 'User created: ' . $user_id;
}

Top ↑

Updating Users #Updating Users

1
2
3
wp_update_user(
    mixed $userdata
);

Updates a single user in the database. The update data is passed along in the $userdata array/object.

To update a single piece of user meta data, use update_user_meta() instead.
To create a new user, use wp_insert_user() instead.

Note:If current user’s password is being updated, then the cookies will be cleared!

Please refer to the Function Reference about wp_update_user() for full explanation about the used parameters.

Top ↑

Example Update #Example Update

Below is an example showing how to update a user’s website profile field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$user_id = 1;
$website = 'https://wordpress.org';
$user_id = wp_update_user(
    [
        'ID'       => $user_id,
        'user_url' => $website,
    ]
);
if (is_wp_error($user_id)) {
    // error
} else {
    // success
}

Top ↑

Deleting Users #Deleting Users

1
2
3
4
wp_delete_user(
    int $id,
    int $reassign = null
);

Delete the user and optionally reassign associated entities to another user ID.

Note:The function performs the action deleted_user after the user have been deleted.

Alert:If the $reassign parameter is not set to a valid user ID, then all entities belonging to the deleted user will be deleted!

Please refer to the Function Reference about wp_delete_user() for full explanation about the used parameters.

Working with User Metadata

Introduction #Introduction

WordPress’ users table was designed to contain only the essential information about the user.

Note:As of WP 4.7 the table contains: ID, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_statusand display_name.

Because of this, to store additional data, the usermeta table was introduced, which can store any arbitrary amount of data about a user.

Both tables are tied together using one-to-many relationship based on the ID in the users table.

Top ↑

Manipulating User Metadata #Manipulating User Metadata

There are two main ways for manipulating User Metadata.

  1. A form field in the user’s profile screen.
  2. Programmatically, via a function call.

via a Form Field

The form field option is suitable for cases where the user will have access to the WordPress admin area, in which he will be able to view and edit profiles.

Before we dive into an example, it’s important to understand the hooks involved in the process and why they are there.

edit_user_profile hook #edit_user_profilehook

This action hook is fired whenever a user edits it’s own user profile.

Remember, a user that doesn’t have the capability of editing his own profile won’t fire this hook.

Top ↑

show_user_profile hook #show_user_profilehook

This action hook is fired whenever a user edits a user profile of somebody else.

Remember, a user that doesn’t have the capability for editing 3rd party profiles won’t fire this hook.

Top ↑

Example Form Field #Example Form Field

In the example below we will be adding a birthday field to the all profile screens. Saving it to the database on profile updates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?php
/**
 * The field on the editing screens.
 *
 * @param $user WP_User user object
 */
function wporg_usermeta_form_field_birthday($user)
{
    ?>
    <h3>It's Your Birthday</h3>
    <table class="form-table">
        <tr>
            <th>
                <label for="birthday">Birthday</label>
            </th>
            <td>
                <input type="date"
                       class="regular-text ltr"
                       id="birthday"
                       name="birthday"
                       value="<?= esc_attr(get_user_meta($user->ID, 'birthday', true)); ?>"
                       title="Please use YYYY-MM-DD as the date format."
                       pattern="(19[0-9][0-9]|20[0-9][0-9])-(1[0-2]|0[1-9])-(3[01]|[21][0-9]|0[1-9])"
                       required>
                <p class="description">
                    Please enter your birthday date.
                </p>
            </td>
        </tr>
    </table>
    <?php
}
/**
 * The save action.
 *
 * @param $user_id int the ID of the current user.
 *
 * @return bool Meta ID if the key didn't exist, true on successful update, false on failure.
 */
function wporg_usermeta_form_field_birthday_update($user_id)
{
    // check that the current user have the capability to edit the $user_id
    if (!current_user_can('edit_user', $user_id)) {
        return false;
    }
    // create/update user meta for the $user_id
    return update_user_meta(
        $user_id,
        'birthday',
        $_POST['birthday']
    );
}
// add the field to user's own profile editing screen
add_action(
    'edit_user_profile',
    'wporg_usermeta_form_field_birthday'
);
// add the field to user profile editing screen
add_action(
    'show_user_profile',
    'wporg_usermeta_form_field_birthday'
);
// add the save action to user's own profile editing screen update
add_action(
    'personal_options_update',
    'wporg_usermeta_form_field_birthday_update'
);
// add the save action to user profile editing screen update
add_action(
    'edit_user_profile_update',
    'wporg_usermeta_form_field_birthday_update'
);

Programmatically #Programmatically

This option is suitable for cases where you’re building a custom user area and/or plan to disable access to the WordPress admin area.

The functions available for manipulating User Metadata are: add_user_meta(), update_user_meta(), delete_user_meta() and get_user_meta().

Top ↑

Add #Add

1
2
3
4
5
6
add_user_meta(
    int $user_id,
    string $meta_key,
    mixed $meta_value,
    bool $unique = false
);

Please refer to the Function Reference about add_user_meta() for full explanation about the used parameters.

Top ↑

Update #Update

1
2
3
4
5
6
update_user_meta(
    int $user_id,
    string $meta_key,
    mixed $meta_value,
    mixed $prev_value = ''
);

Please refer to the Function Reference about update_user_meta() for full explanation about the used parameters.

Top ↑

Delete #Delete

1
2
3
4
5
delete_user_meta(
    int $user_id,
    string $meta_key,
    mixed $meta_value = ''
);

Please refer to the Function Reference about delete_user_meta() for full explanation about the used parameters.

Top ↑

Get #Get

1
2
3
4
5
get_user_meta(
    int $user_id,
    string $key = '',
    bool $single = false
);

Please refer to the Function Reference about get_user_meta() for full explanation about the used parameters.

Please note, if you pass only the $user_id, the function will retrieve all Metadata as an associative array.

You can render User Metadata anywhere in your plugin or theme.

Roles and Capabilities

Roles and capabilities are two important aspects of WordPress that allow you to control user privileges.

WordPress stores the Roles and their Capabilities in the options table under the user_roles key.

Roles #Roles

A role defines a set of capabilities for a user. For example, what the user may see and do in his dashboard.

By default, WordPress have six roles:

  • Super Admin
  • Administrator
  • Editor
  • Author
  • Contributor
  • Subscriber

More roles can be added and the default roles can be removed.

Adding Roles #Adding Roles

Add new roles and assign capabilities to them with add_role().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function wporg_simple_role()
{
    add_role(
        'simple_role',
        'Simple Role',
        [
            'read'         => true,
            'edit_posts'   => true,
            'upload_files' => true,
        ]
    );
}
// add the simple_role
add_action('init', 'wporg_simple_role');

Alert:After the first call to add_role(), the Role and it’s Capabilities will be stored in the database!

Sequential calls will do nothing: including altering the capabilities list, which might not be the behavior that you’re expecting.

Note:To alter the capabilities list in bulk: remove the role using remove_role() and add it again using add_role() with the new capabilities.

Make sure to do it only if the capabilities differ from what you’re expecting (i.e. condition this) or you’ll degrade performance considerably!

Top ↑

Removing Roles #Removing Roles

Remove roles with remove_role().

1
2
3
4
5
6
7
function wporg_simple_role_remove()
{
    remove_role('simple_role');
}
// remove the simple_role
add_action('init', 'wporg_simple_role_remove');

Alert:After the first call to remove_role(), the Role and it’s Capabilities will be removed from the database!

Sequential calls will do nothing.

Note:If you’re removing the default roles:

  • We advise against removing the Administrator and Super Admin roles!
  • Make sure to keep the code in your plugin/theme as future WordPress updates may add these roles again.
  • Run
    update_option('default_role', YOUR_NEW_DEFAULT_ROLE)
    since you’ll be deleting subscriber which is WP’s default role.

Top ↑

Capabilities #Capabilities

Capabilities define what a role can and can not do: edit posts, publish posts, etc.

Note:Custom post types can require a certain set of Capabilities.

Top ↑

Adding Capabilities #Adding Capabilities

You may define new capabilities for a role.

Use get_role() to get the role object, then use the add_cap() method of that object to add a new capability.

1
2
3
4
5
6
7
8
9
10
11
function wporg_simple_role_caps()
{
    // gets the simple_role role object
    $role = get_role('simple_role');
    // add a new capability
    $role->add_cap('edit_others_posts', true);
}
// add simple_role capabilities, priority must be after the initial role definition
add_action('init', 'wporg_simple_role_caps', 11);

Note:It’s possible to add custom capabilities to any role.

Under the default WordPress admin, they would have no effect, but they can be used for custom admin screen and front-end areas.

Top ↑

Removing Capabilities #Removing Capabilities

You may remove capabilities from a role.

The implementation is similar to Adding Capabilities with the difference being the use of remove_cap() method for the role object.

Top ↑

Using Roles and Capabilities #Using Roles and Capabilities

Top ↑

Get Role #Get Role

Get the role object including all of it’s capabilities with get_role().

1
2
3
get_role(
    string $role
);

Top ↑

User Can #User Can

Check if a user have a specified role or capability with user_can().

1
2
3
4
user_can(
    int|object $user,
    string $capability
);

Warning:There is an undocumented, third argument, $args, that may include the object against which the test should be performed.

E.g. Pass a post ID to test for the capability of that specific post.

Top ↑

Current User Can #Current User Can

current_user_can() is a wrapper function for user_can() using the current user object as the $user parameter.

Use this in scenarios where back-end and front-end areas should require a certain level of privileges to access and/or modify.

1
2
3
current_user_can(
    string $capability
);

Top ↑

Example #Example

Here’s a practical example of adding an Edit link on the in a template file if the user has the proper capability:

1
2
3
if (current_user_can('edit_posts')) {
    edit_post_link('Edit', '<p>', '</p>');
}

Top ↑

Multisite #Multisite

The current_user_can_for_blog() function is used to test if the current user has a certain role or capability on a specific blog.

1
2
3
4
current_user_can_for_blog(
    int $blog_id,
    string $capability
);

Top ↑

Reference #Reference

Codex Reference for User Roles and Capabilities.

HTTP API

Introduction #Introduction

HTTP stands for Hypertext Transfer Protocol and is the foundational communication protocol for the entire Internet. Even if this is your first experience with HTTP it’s likely that you probably understand more than you realize. At its most basic level, HTTP works like this:

  • “Hello server XYZ, may I please have file abc.html”
  • “Well hello there little client, yes you may, here it is”

There are many different methods to send HTTP requests in PHP. The purpose of the WordPress HTTP API is to support as many of those methods as possible and use the one that is the most suitable for the particular request.

The WordPress HTTP API can also be used to communicate and interact with with other APIs like the Twitter API or the Google Maps API.

HTTP methods #HTTP methods

HTTP has several methods, or verbs, that describe particular types of actions. Though a couple more exist, WordPress has pre-built functions for three of the most common. Whenever an HTTP request is made a method is also passed with it to help the server determine what kind of action the client is requesting.

GET #GET

GET is used to retrieve data. This is by far the most commonly used verb. Every time you view a website or pull data from an API you are seeing the result of a GET request. In fact your browser sent a GET request to the server you are reading this on and requested the data used to build this very article.

Top ↑

POST #POST

POST is used to send data to the server for the server to act upon in some way. For example, a contact form. When you enter data into the form fields and click the submit button the browser takes the data and sends a POST request to the server with the text you entered into the form. From there the server will process the contact request.

Top ↑

HEAD is much less well known than the other two. HEAD is essentially the same as a GET request except that it does not retrieve the data, only information about the data. This data describes such things as when the data was last updated, whether the client should cache the data, what type the data is, etc. Modern browsers often send HEAD requests to pages you have previously visited to determine if there are any updates. If not, you may actually be seeing a previously downloaded copy of the page instead of using bandwidth needlessly pulling in the same copy.

All good API clients utilize HEAD before performing a GET request to potentially save on bandwidth. Though it will require two separate HTTP requests if HEAD says there is new data, the data size with a GET request can be very large. Only using GET when HEAD says the data is new or should not be cached will help save on expensive bandwidth and load times.

Top ↑

Custom Methods #Custom Methods

There are other HTTP methods, such as PUT, DELETE, TRACE, and CONNECT. These methods will not be covered in this article as there aren’t pre-built methods to utilize them in WordPress, nor is it yet common for APIs to implement them.

Depending upon how your server is configured you can also implement additional HTTP methods of your own. It is always a gamble to go outside of the standard methods, and places huge potential limitations on other developers creating clients to consume your site or API, however it is possible to utilize any method you wish with WordPress. We will briefly touch on how to do that in this article.

Top ↑

Response codes #Response codes

HTTP utilizes both numeric and string response codes. Rather than go into a lengthy explanation of each, here are the the standard response codes. You can define your own response codes when creating APIs, however unless you need to support specific types of responses it may be best to stick to the standard codes. Custom codes are usually in the 1xx ranges.

Top ↑

Code Classes #Code Classes

The type of response can quickly be seen by the leftmost digit of the three digit codes.

Status Code Description
2xx Request was successful
3xx Request was redirected to another URL
4xx Request failed due to client error. Usually invalid authentication or missing data
5xx Request failed due to a server error. Commonly missing or misconfigured configuration files

 Common Codes

These are the most commons codes you will encounter.

Status Code Description
200 OK – Request was successful
301 Resource was moved permanently
302 Resource was moved temporarily
403 Forbidden – Usually due to an invalid authentication
404 Resource not found
500 Internal server error
503 Service unavailable

Top ↑

GETting data from an API #GETting data from an API

GitHub provides an excellent API that does not require app registration for many public aspects, so to demonstrate some of these methods, examples will target the GitHub API.

GETting data is made incredibly simple in WordPress through the wp_remote_get() function. This function takes the following two arguments:

  1. $url – Resource to retrieve data from. This must be in a standard HTTP format
  2. $args – OPTIONAL – You may pass an array of arguments in here to alter behavior and headers, such as cookies, follow redirects, etc.

The following defaults are assumed, though they can be changed via the $args parameter:

  • method – GET
  • timeout – 5 – How long to wait before giving up
  • redirection – 5 – How many times to follow redirections.
  • httpversion – 1.0
  • blocking – true – Should the rest of the page wait to finish loading until this operation is complete?
  • headers – array()
  • body – null
  • cookies – array()

Let’s use the URL to a GitHub user account and see what sort of information we can get

1
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );

$response will contain all the headers, content, and other meta data about our request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Array
(
    [headers] => Array
        (
            [server] => nginx
            [date] => Fri, 05 Oct 2012 04:43:50 GMT
            [content-type] => application/json; charset=utf-8
            [connection] => close
            [status] => 200 OK
            [vary] => Accept
            [x-ratelimit-remaining] => 4988
            [content-length] => 594
            [last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
            [etag] => "5d5e6f7a09462d6a2b473fb616a26d2a"
            [x-github-media-type] => github.beta
            [cache-control] => public, s-maxage=60, max-age=60
            [x-content-type-options] => nosniff
            [x-ratelimit-limit] => 5000
        )
    [body] => {"type":"User","login":"blobaugh","gravatar_id":"f25f324a47a1efdf7a745e0b2e3c878f","public_gists":1,"followers":22,"created_at":"2011-05-23T21:38:50Z","public_repos":31,"email":"ben@lobaugh.net","hireable":true,"blog":"http://ben.lobaugh.net","bio":null,"following":30,"name":"Ben Lobaugh","company":null,"avatar_url":"https://secure.gravatar.com/avatar/f25f324a47a1efdf7a745e0b2e3c878f?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","id":806179,"html_url":"https://github.com/blobaugh","location":null,"url":"https://api.github.com/users/blobaugh"}
    [response] => Array
        (
            [preserved_text 5237511b45884ac6db1ff9d7e407f225 /] => 200
            [message] => OK
        )
    [cookies] => Array
        (
        )
    [filename] =>
)

All of the same helper functions can be used on this function as with the previous two. The exception here being that HEAD never returns a body, so that element will always be empty.

Top ↑

GET the body you always wanted #GET the body you always wanted

Just the body can be retrieved using wp_remote_retrieve_body(). This function takes just one parameter, the response from any of the other wp_remote_Xfunctions where retrieve is not the next value.

1
2
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$body = wp_remote_retrieve_body( $response );

Still using the GitHub resource from the previous example, $body will be

1
{"type":"User","login":"blobaugh","public_repos":31,"gravatar_id":"f25f324a47a1efdf7a745e0b2e3c878f","followers":22,"avatar_url":"https://secure.gravatar.com/avatar/f25f324a47a1efdf7a745e0b2e3c878f?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","public_gists":1,"created_at":"2011-05-23T21:38:50Z","email":"ben@lobaugh.net","following":30,"name":"Ben Lobaugh","company":null,"hireable":true,"id":806179,"html_url":"https://github.com/blobaugh","blog":"http://ben.lobaugh.net","location":null,"bio":null,"url":"https://api.github.com/users/blobaugh"}

If you do not have any other operations to perform on the response other than getting the body you can reduce the code to one line with

1
$body = wp_remote_retrieve_body( wp_remote_get( 'https://api.github.com/users/blobaugh' ) );

Many of these helper functions can be used on one line similarly.

Top ↑

GET the response code #GET the response code

You may want to check the response code to ensure your retrieval was successful. This can be done via the wp_remote_retrieve_response_code()function:

1
2
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$http_code = wp_remote_retrieve_response_code( $response );

If successful $http_code will contain 200.

Top ↑

GET a specific header #GET a specific header

If your desire is to retrieve a specific header, say last-modified, you can do so with wp_remote_retrieve_header(). This function takes two parameters

  1. $response – The response from the get call
  2. $header – Name of the header to retrieve

To retrieve the last-modified header

1
2
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$last_modified = wp_remote_retrieve_header( $response, 'last-modified' );

$last_modified will contain [last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
You can also retrieve all of the headers in an array with wp_remote_retrieve_headers( $response ).

Top ↑

GET using basic authentication #GET using basic authentication

APIs that are secured more provide one or more of many different types of authentication. A common, though not highly secure, authentication method is HTTP Basic Authentication. It can be used in WordPress by passing ‘Authorization’ to the second parameter of the wp_remote_get() function, as well as the other HTTP method functions.

1
2
3
4
5
6
$args = array(
    'headers' => array(
        'Authorization' => 'Basic ' . base64_encode( YOUR_USERNAME . ':' . YOUR_PASSWORD )
    )
);
wp_remote_get( $url, $args );

Top ↑

POSTing data to an API #POSTing data to an API

The same helper methods (wp_remote_retrieve_body(), etc ) are available for all of the HTTP method calls, and utilized in the same fashion.

POSTing data is done using the wp_remote_post() function, and takes exactly the same parameters as wp_remote_get(). It should be noted here that you are required to pass in ALL of the elements in the array for the second parameter. The Codex provides the default acceptable values. You only need to care right now about the data you are sending so the other values will be defaulted.

To send data to the server you will need to build an associative array of data. This data will be assigned to the 'body' value. From the server side of things the value will appear in the $_POST variable as you would expect. i.e. if body => array( 'myvar' => 5 ) on the server $_POST['myvar'] = 5.

Because GitHub does not allow POSTing to the API used in the previous example, this example will pretend that it does. Typically if you want to POST data to an API you will need to contact the maintainers of the API and get an API key or some other form of authentication token. This simply proves that your application is allowed to manipulate data on the API the same way logging into a website as a user does to the website.

Lets assume we are submitting a contact form with the following fields: name, email, subject, comment. To setup the body we do the following:

1
2
3
4
5
6
$body = array(
    'name' => 'Jane Smith',
    'email' => 'some@email.com',
    'subject' => 'Checkout this API stuff',
    'comment' => 'I just read a great tutorial by this Ben Lobaugh. It taught me amazing things about interacting with APIs in WordPress! You gotta check it out!'
);

Now we need to set up the rest of the values that will be passed to the second parameter of wp_remote_post()

1
2
3
4
5
6
7
8
9
$args = array(
    'body' => $body,
    'timeout' => '5',
    'redirection' => '5',
    'httpversion' => '1.0',
    'blocking' => true,
    'headers' => array(),
    'cookies' => array()
);

Then of course to make the call

1
$response = wp_remote_post( 'http://your-contact-form.com', $args );

For those of you that do not like piecing together chunks of code here is the entire snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$body = array(
    'name' => 'Jane Smith',
    'email' => 'some@email.com',
    'subject' => 'Checkout this API stuff',
    'comment' => 'I just read a great tutorial by this Ben Lobaugh. It taught me amazing things about interacting with APIs in WordPress! You gotta check it out!'
);
$args = array(
    'body' => $body,
    'timeout' => '5',
    'redirection' => '5',
    'httpversion' => '1.0',
    'blocking' => true,
    'headers' => array(),
    'cookies' => array()
);
$response = wp_remote_post( 'http://your-contact-form.com', $args );

 

Top ↑

HEADing off bandwidth usage #HEADing off bandwidth usage

It can be pretty important, and sometimes required by the API, to check a resource status using HEAD before retrieving it. On high traffic APIs, GET is often limited to number of requests per minute or hour. There is no need to even attempt a GET request unless the HEAD request shows that the data on the API has been updated.

As mentioned previously, HEAD contains data on whether or not the data has been updated, if the data should be cached, when to expire the cached copy, and sometimes a rate limit on requests to the API.

Going back to the GitHub example, here are are few headers to watch out for. Most of these headers are standard, but you should always check the API docs to ensure you understand which headers are named what, and their purpose.

  • x-ratelimit-limit – Number of requests allowed in a time period
  • x-ratelimit-remaining – Number of remaining available requests in time period
  • content-length – How large the content is in bytes. Can be useful to warn the user if the content is fairly large
  • last-modified – When the resource was last modified. Highly useful to caching tools
  • cache-control – How should the client handle caching

The following will check the HEAD value of my GitHub user account:

1
$response = wp_remote_head( 'https://api.github.com/users/blobaugh' );

$response should look similar to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Array
(
    [headers] => Array
        (
            [server] => nginx
            [date] => Fri, 05 Oct 2012 05:21:26 GMT
            [content-type] => application/json; charset=utf-8
            [connection] => close
            [status] => 200 OK
            [vary] => Accept
            [x-ratelimit-remaining] => 4982
            [content-length] => 594
            [last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
            [etag] => "5d5e6f7a09462d6a2b473fb616a26d2a"
            [x-github-media-type] => github.beta
            [cache-control] => public, s-maxage=60, max-age=60
            [x-content-type-options] => nosniff
            [x-ratelimit-limit] => 5000
        )
    [body] =>
    [response] => Array
        (
            [preserved_text 39a8515bd2dce2aa06ee8a2a6656b1de /] => 200
            [message] => OK
        )
    [cookies] => Array
        (
        )
    [filename] =>
)

All of the same helper functions can be used on this function as with the previous two. The exception here being that HEAD never returns a body, so that element will always be empty.

Top ↑

Make any sort of request #Make any sort of request

If you need to make a request using an HTTP method that is not supported by any of the above functions do not panic. The great people developing WordPress already thought of that and lovingly provided wp_remote_request(). This function takes the same two parameters as wp_remote_get(), and allows you to specify the HTTP method as well. What data you need to pass along is up to your method.

To send a DELETE method example you may have something similar to the following:

1
2
3
4
$args = array(
    'method' => 'DELETE'
);
$response = wp_remote_request( 'http://some-api.com/object/to/delete', $args );

Top ↑

Introduction to caching #Introduction to caching

Caching is a practice whereby commonly used objects or objects requiring significant time to build are saved into a fast object store for quick retrieval on later requests. This prevents the need to spend the time fetching and building the object again. Caching is a vast subject that is part of website optimization and could go into an entire series of articles by itself. What follows is just an introduction to caching and a simple yet effective way to quickly setup a cache for API responses.

Why should you cache API responses? Well, the big elephant in the room is because external APIs slow down your site. Many consultants will tell you tapping into external APIs will improve the performance of your website by reducing the amount of connections and processing it performs, as well as costly bandwidth, but sometimes this is simply not true.

It is a fine balancing act between the speed your server can send data and the amount of time it takes for the remote server to process a request, build the data, and send it back. The second glaring aspect is that many APIs have a limited number of requests in a time period, and possibly a limit to the number of connections by an application at once. Caching helps solve these dilemmas by placing a copy of the data on your server until it needs to be refreshed.

Top ↑

When should you cache? #When should you cache?

The snap answer to this is *always*, but again there are times when you should not. If you are dealing with real time data or the API specifically says not to cache in the headers you may not want to cache, but for all other situations it is generally a good idea to cache any resources retrieved from an API.

Top ↑

WordPress Transients #WordPress Transients

WordPress Transients provide a convenient way to store and use cached objects. Transients live for a specified amount of time, or until you need them to expire when a resource from the API has been updated. Using the transient functionality in WordPress may be the easiest to use caching system you ever encounter. There are only three function to do all the heavy lifting for you.

Top ↑

Cache an object ( Set a transient ) #Cache an object ( Set a transient )

Caching an object is done with the set_transient() function. This function takes the following three parameters:

  1. $transient – Name of the transient for future reference
  2. $value – Value of the transient
  3. $expiration – How many seconds from saving the transient until it expires

An example of caching the GitHub user information response from above for one hour would be

1
2
3
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
set_transient( 'blobaugh_github_userinfo', $response, 60*60 );

Top ↑

Get a cached object ( Get a transient ) #Get a cached object ( Get a transient )

Getting a cached object is quite a bit more complex than setting a transient. You need to request the transient, but then you also need to check to see if that transient has expired and if so fetch updated data. Usually the set_transient()call is made inside of the get_transient() call. Here is an example of getting the transient data for the GitHub user profile:

1
2
3
4
5
6
7
8
$github_userinfo = get_transient( 'blobaugh_github_userinfo' );
if( false === $github_userinfo ) {
    // Transient expired, refresh the data
    $response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
    set_transient( 'blobaugh_github_userinfo', $response, 60*60 );
}
// Use $github_userinfo as you will

Top ↑

Delete a cached object (Delete a transient) #Delete a cached object (Delete a transient)

Deleting a cached object is the easiest of all the transient functions, simply pass it a parameter of the name of the transient and you are done.

To remove the Github user info:

1
delete_transient( 'blobaugh_github_userinfo');

JavaScript

JavaScript is an important component in many WordPress plugins.  WordPress comes with a variety of JavaScript libraries bundled with core. One of the most commonly-used libraries in WordPress is jQuery because it is lightweight and easy to use. jQuery can be used in your plugin to manipulate the DOM object or to perform Ajax actions.

jQuery

Using jQuery #Using jQuery

Your jQuery script runs on the user’s browser after your WordPress webpage is received. A basic jQuery statement has two parts: a selector that determines which HTML elements the code applies to, and an action or event, which determines what the code does or what it reacts to. The basic event statement looks like this:

1
jQuery.(selector).event(function);

When an event, such as a mouse click, occurs in an HTML element selected by the selector, the function that is defined inside the final set of parentheses is executed.

All the following code examples are based on this HTML page content. Assume it appears on your plugin’s admin settings screen, defined by the file myplugin_settings.php. It is a simple table with radio buttons next to each title.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre>
<formid="radioform">
 <table>
  <tbody>
   <tr>
    <td><inputclass="pref"checked="checked"name="book"type="radio"value="Sycamore Row"/>Sycamore Row</td>
    <td>John Grisham</td>
   </tr>
   <tr>
    <td><inputclass="pref"name="book"type="radio"value="Dark Witch"/>Dark Witch</td>
    <td>Nora Roberts</td>
   </tr>
  </tbody>
 </table>
</form>
<pre>

The output could look something like this on your settings page.

sample table

In the article on AJAX, we will build an AJAX exchange that saves the user selection in usermeta and adds the number of posts tagged with the selected title. Not a very practical application, but it illustrates all the important steps. jQuery code can either reside in an external file or be output to the page inside a <script> block. We will focus on the external file variation because passing values from PHP requires special attention. The same code can be output to the page if that seems more expedient to you.

Selector and Event #Selector and Event

The selector is the same form as CSS selectors: ".class" or "#id". There’s many more forms, but these are the two you will frequently use. In our example, we will use class ".pref". There’s also a slew of possible events, one you will likely use a lot is ‘click’. In our example we will use ‘change’ to capture a radio button selection. Be aware that jQuery events are often named somewhat differently than those with JavaScript. So far, after we add in an empty anonymous function, our example statement looks like this:

1
2
3
$.(".pref").change(function(){
    /*do stuff*/
});

This code will “do stuff” when any element of the “pref” class changes.

Note: This code snippet, and all examples on this page, are for illustrating the use of AJAX. The code is not suitable for production environments because related operations such as sanitization, security, error handling, and internationalization have been intentionally omitted. Be sure to always address these important operations in your production code.

Ajax

What is Ajax? #What is Ajax?

Ajax is the acronym for Asynchronous JavaScript And XML. XML is a data exchange format and UX is software developer shorthand for User Experience. Ajax is an Internet communications technique that allows a web page displayed in a user’s browser to request specific information from a server and display this new information on the same page without the need to reload the entire page. You can already imagine how this improves the user experience.

While XML is the traditional data exchange format used, the exchange can actually be any convenient format. When working with PHP code, many developers favor JSON because the internal data structure created from the transmitted data stream is easier to interface with.

To see Ajax in action, go to your WordPress administration area and add a category or tag. Pay close attention when you click the Add New button, notice the page changes but does not actually reload. Not convinced? Check your browser’s back history, if the page had reloaded, you would see two entries for the page.

Ajax does not even require a user action to work. Google Docs automatically saves your document every few minutes with Ajax without you needing to initiate a save action.

Top ↑

Why use Ajax? #Why use Ajax?

Obviously, it improves the user experience. Instead of presenting a boring, static page, Ajax allows you to present a dynamic, responsive, user friendly experience. Users can get immediate feedback that some action they took was right or wrong. No need to submit an entire form before finding out there is a mistake in one field. Important fields can be validated as soon as the data is entered. Or suggestions could be made as the user types.

Ajax can dramatically decrease the amount of data flowing back and forth. Only the pertinent data needs to be exchanged instead of all of the page content, which is what happens when the page reloads.

Specifically related to WordPress plugins, Ajax is by far the best way to initiate a process independent of WordPress content. If you’ve programmed PHP before, you would likely do this by simply linking to a new PHP page. The user following the link initiates the process. The problem with this is that you cannot access any WordPress functions when you link to a new external PHP page. In the past, developers accessed WordPress functions by including the core file wp-load.phpon their new PHP page. The problem with doing that is you cannot possibly know the correct path to this file anymore. The WordPress architecture is now flexible enough that the /wp-content/ and your plugin files can be moved from its usual location to one level from the installation root. You cannot know where wp-load.php is relative to your plugin files, and you cannot know the absolute path to the installation folder either.

What you can know is where to send an Ajax request, because it is defined in a global JavaScript variable. Your PHP Ajax handler script is actually an action hook, so all WordPress functions are automatically available to it, unlike an external PHP file.

Top ↑

How Do I Use Ajax? #How Do I Use Ajax?

If you are new to WordPress but have experience using Ajax in other environments, you will need to relearn a few things. The way WordPress implements Ajax is most likely different than what you are used to. If everything is new to you, no problem. You will learn the basics here. Once you’ve developed a basic Ajax exchange, it’s a cinch to expand on that base and develop that killer app with an awesome user interface!

There are two major components of any Ajax exchange in WordPress. The client side JavaScript or jQuery and the server side PHP. All Ajax exchanges follow the following sequence of events.

  1. Some sort of page event initiates a JavaScript or jQuery function. That function gathers some data from the page and sends it via a HTTP request to the server.Because handling HTTP requests with JavaScript is awkward and jQuery is bundled into WordPress anyway, we are going to focus only on jQuery code from here on out. Ajax with straight JavaScript is possible, but it’s not worth doing it when jQuery is available.
  2. The server receives the request and does something with the data. It may assemble related data and send it back to the client browser in the form of an HTTP response. This is not a requirement, but since keeping the user informed about what’s going on is desirable, it’s very rare not to send some kind of response.
  3. The jQuery function that sent the initial Ajax request receives the server response and does something with it. It may update something on the page and/or present a message to the user by some means.

Top ↑

Using Ajax with jQuery #Using Ajax with jQuery

Now we will define the “do stuff” portion from the snippet in the article on jQuery. We will use the $.post() method, which takes 3 parameters: the URL to send the POST request to, the data to send, and a callback function to handle the server response. Before we do that though, we have a bit of advance planning to get out of the way. We do the following assignment for use later in the callback function. The purpose will be more evident in the Callback section.

1
var this2 = this;

URL #URL

All WordPress Ajax requests must be sent to wp-admin/admin-ajax.php. The correct, complete URL needs to come from PHP, jQuery cannot determine this value on its own, and you cannot hardcode the URL in your jQuery code and expect anyone else to use your plugin on their site. If the page is from the administration area, WordPress sets the correct URL in the global JavaScript variable ajaxurl. For a page from the public area, you will need to establish the correct URL yourself and pass it to jQuery using wp_localize_script(). This will be covered in more detail in the PHP section. For now just know that the URL that will work for both the front and back end is available as a property of a global object that you will define in the PHP segment. In jQuery it is referenced like so:

1
my_ajax_obj.ajax_url

Top ↑

Data #Data

All data that needs to be sent to the server is included in the data array. Besides any data needed by your app, you must send an action parameter. For requests that could result in a change to the database you need to send a nonce so the server knows the request came from a legitimate source. Our example data array provided to the .post() method looks like this:

1
2
3
4
{_ajax_nonce: my_ajax_obj.nonce, //nonce
  action: "my_tag_count",        //action
  title: this.value              //data
}

Each component is explained below.

Top ↑

Nonce #Nonce

Nonce is a portmanteau of “Number used ONCE”. It is essentially a unique hexadecimal serial number assigned to each instance of any form served. The nonce is established with PHP script and passed to jQuery the same way the URL was, as a property in a global object. In this case it is referenced as my_ajax_obj.nonce.

Note:

A true nonce needs to be refreshed every time it is used so the next Ajax call has a new, unused nonce to send as verification. As it happens, the WordPress nonce implementation is not a true nonce. The same nonce can be used as many times as necessary in a 24 hour period. Generating a nonce with the same seed phrase will always yield the same number for a 12 hour period after which a new number will finally be generated.

If your app needs serious security, implement a true nonce system where the server sends a new, fresh nonce in response to an Ajax request for the script to use to verify the next request.

It’s easiest if you key this nonce value to _ajax_nonce. You can use a different key if it’s coordinated with the PHP code verifying the nonce, but it’s easier to just use the default value and not worry about coordination. Here is the way the declaration of this key-value pair appears:

1
_ajax_nonce: my_ajax_obj.nonce

Top ↑

Action #Action

All WordPress Ajax requests must include an action argument in the data. This value is an arbitrary string that is used in part to construct an action tag you use to hook your Ajax handler code. It’s useful for this value to be a very brief description of the Ajax call’s purpose. Unsurprisingly, the key for this value is ‘action’. In this example, we will use "my_tag_count" as our action value. The declaration of this key-value pair looks like this:

1
action: "my_tag_count"

Any other data the server needs to do its task is also included in this array. If there are a lot of fields to transmit, there are two common formats to combine data fields into a single string for more convenient transmission, XML and JSON. Using these formats is optional, but whatever you do does need to be coordinated with the PHP script on the server side. More information on these formats is available in the following Callback section. It is more common to receive data in this format than to send it, but it can work both ways.

In our example, the server only needs one value, a single string for the selected book title, so we will use the key ‘title’. In jQuery, the object that fired the event is always contained in the variable this. Accordingly, the value of the selected element is this.value. Our declaration of this key-value pair appears like so:

1
title: this.value

Top ↑

Callback #Callback

The callback handler is the function to execute when a response comes back from the server after the request is made. Once again, we usually see an anonymous function here. The function is passed one parameter, the server response. The response could be anything from a yes or no to a huge XML database. JSON formatted data is also a useful format for data. The response is not even required. If there is none, then no callback need be specified. In the interest of UX, it’s always a good idea to let the user know what happened to any request, so it is recommended to always respond and provide some indication that something happened.

In our example, we replace the current text following the radio input with the server response, which includes the number of posts tagged by the book title. Here is our anonymous callback function:

1
2
3
4
function(data) {
    this2.nextSibling.remove();
    $(this2).after(data);
}

data contains the entire server response. Earlier we assigned to this2 the object that triggered the change event (referenced as this) with the line var this2 = this;. This is because variable scope in closures only extends one level. By assigning this2 in the event handler (the part that initially just contained “/* do stuff */”), we are able to use it in the callback where this would be out of scope.

The server response can take on any form. Significant quantities of data should be encoded into a data stream for easier handling. XML and JSON are two common encoding schemes.

XML #XML

XML is the classic data exchange format for Ajax. It is after all the ‘X’ in Ajax. It continues to be a viable exchange format even though it can be difficult to work with using native PHP functions. Many PHP programmers prefer the JSON exchange format for that reason. If you do use XML, the parsing method depends on the browser being used. Use Microsoft.XMLDOM ActiveX for Internet Explorer and use DOMParser for everything else.

Top ↑

JSON #JSON

JSON is often favored for its light weight and ease of use. You can actually parse JSON using eval(), but don’t do that! The use of eval() carries significant security risks. Instead, use a dedicated parser, which is also faster. Use the global instance of the parser object JSON. To ensure that it is available, be sure it is enqueued with other scripts on the page. More information about enqueuing is included later in the PHP section.

Top ↑

Other #Other

As long as the data format is coordinated with the PHP handler, it can be any format you like, such as comma delimited, tab delimited, or any kind of structure that works for you.

Top ↑

Client Side Summary #Client Side Summary

Now that we’ve added our callback as the final parameter for the $.post()function, we’ve completed our sample jQuery Ajax script. All the pieces put together look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
jQuery(document).ready(function($) {           //wrapper
    $(".pref").change(function() {             //event
        var this2 = this;                      //use in callback
        $.post(my_ajax_obj.ajax_url, {         //POST request
           _ajax_nonce: my_ajax_obj.nonce,     //nonce
            action: "my_tag_count",            //action
            title: this.value                  //data
        }, function(data) {                    //callback
            this2.nextSibling.remove();        //remove current title
            $(this2).after(data);              //insert server response
        });
    });
});

This script can either be output into a block on the web page or contained in its own file. This file can reside anywhere on the Internet, but most plugin developers place it in a /js/ subfolder of the plugin’s main folder. Unless you have reason to do otherwise, you may as well follow convention. For this example we will name our file myjquery.js

Server Side PHP and Enqueuing

There are two parts to the server side PHP script that are needed to implement AJAX communication. First we need to enqueue the jQuery script on the web page and localize any PHP values that the jQuery script needs. Second is the actual handling of the AJAX request.

Enqueue Script #Enqueue Script

This section covers the two major quirks of AJAX in WordPress that trip up experienced coders new to WordPress. One is the need to enqueue scripts in order to get meta links to appear correctly in the page’s head section. The other is that all AJAX requests need to be sent through wp-admin/admin-ajax.php. Never send requests directly to your plugin pages.

Enqueue #Enqueue

Use the function wp_enqueue_script() to get WordPress to insert a meta link to your script in the page’s section. Never hardcode such links in the header template. As a plugin developer, you do not have ready access to the header template, but this rule bears mentioning anyway.

The enqueue function takes three parameters. The first is an arbitrary tag or handle that is used to refer to your script in other functions. The second is the complete URL to your script file. For portability, use plugins_url() to build the proper URL. If you are enqueuing the script for something besides a plugin, use some related function to create a proper URL – never hardcode it. The third parameter is an array of any script tags that your script is dependent on. Since we are using jQuery to send an AJAX request, you will at least need to list ‘jquery’ in the array. Always use an array even if it is for a single dependency. The enqueue call for our example looks like this:

1
2
3
4
wp_enqueue_script( 'ajax-script',
    plugins_url( '/js/myjquery.js', __FILE__ ),
    array('jquery')
);

You cannot enqueue scripts directly from your plugin code page when it is loaded. Scripts must be enqueued from one of a few action hooks – which one depends on what sort of page the script needs to be linked to. For administration pages, use admin_enqueue_scripts. For front-end pages use wp_enqueue_scripts, except for the login page, in which case use login_enqueue_scripts.

The admin_enqueue_scripts hook passes the current page filename to your callback. Use this information to only enqueue your script on pages where it is needed. The front-end version does not pass anything. In that case, use template tags such as is_home(), is_single(), etc. to ensure that you only enqueue your script where it is needed. This is the complete enqueue code for our example:

1
2
3
4
5
6
7
8
add_action( 'admin_enqueue_scripts', 'my_enqueue' );
function my_enqueue( $hook ) {
    if( 'myplugin_settings.php' != $hook ) return;
    wp_enqueue_script( 'ajax-script',
        plugins_url( '/js/myjquery.js', __FILE__ ),
        array( 'jquery' )
    );
}

Why do we use a named function here but use anonymous functions with jQuery? Because closures are only recently supported by PHP. jQuery has supported them for quite some time. Since some people may still be running older versions of PHP, we always use named functions for maximum compatibility. If you have a recent PHP version and are developing only for your own installation, go ahead and use closures if you like.

Register vs. Enqueue #Register vs. Enqueue

You will see examples in other tutorials that religiously use wp_register_script(). This is fine, but its use is optional. What is not optional is wp_enqueue_script(). This function must be called in order for your script file to be properly linked on the web page. So why register scripts? It creates a useful tag or handle with which you can easily reference the script in various parts of your code as needed. If you just need your script loaded and are not referencing it elsewhere in your code, there is no need to register it.

Top ↑

Nonce #Nonce

You need to create a nonce so that the jQuery AJAX request can be validated as a legitimate request instead of a potentially nefarious request from some unknown bad actor. Only your PHP script and your jQuery script will know this value. When the request is received, you can verify it is the same value created here. This is how to create a nonce for our example:

1
$title_nonce = wp_create_nonce( 'title_example' );

The parameter title_example can be any arbitrary string. It’s suggested the string be related to what the nonce is used for, but it can really be anything that suits you.

Top ↑

Localize #Localize

If you recall from the jQuery Section, data created by PHP for use by jQuery was passed in a global object named my_ajax_obj. In our example, this data was a nonce and the complete URL to admin-ajax.php. The process of assigning object properties and creating the global jQuery object is called localizing. This is the localizing code used in our example which uses wp_localize_script().

1
2
3
4
wp_localize_script( 'ajax-script', 'my_ajax_obj', array(
    'ajax_url' => admin_url( 'admin-ajax.php' ),
    'nonce'    => $title_nonce, // It is common practice to comma after
) );                // the last array item for easier maintenance

Note how our script handle ajax-script is used so that the global object is assigned to the right script. The object is global to our script, not to all scripts. Localization can also be called from the same hook that is used to enqueue scripts. The same goes for creating a nonce, though that particular function can be called virtually anywhere. All of that combined together in a single hook callback looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
add_action( 'admin_enqueue_scripts', 'my_enqueue' );
function my_enqueue( $hook ) {
    if( 'myplugin_settings.php' != $hook ) return;
    wp_enqueue_script( 'ajax-script',
        plugins_url( '/js/myjquery.js', __FILE__ ),
        array( 'jquery' )
    );
    $title_nonce = wp_create_nonce( 'title_example' );
    wp_localize_script( 'ajax-script', 'my_ajax_obj', array(
       'ajax_url' => admin_url( 'admin-ajax.php' ),
       'nonce'    => $title_nonce,
    ) );
}

Top ↑

AJAX Action #AJAX Action

The other major part of the server side PHP code is the actual AJAX handler that receives the POSTed data, does something with it, then sends an appropriate response back to the browser. This takes on the form of a WordPress action hook. Which hook tag you use depends on whether the user is logged in or not and what value your jQuery script passed as the action: value.

Note:$_GET , $_POST and $_COOKIE vs $_REQUEST

You’ve probably used one or more of the PHP super globals such as $_GET or $_POST to retrieve values from forms or cookies (using $_COOKIE). Maybe you prefer $_REQUEST instead, or at least have seen it used. It’s kind of cool – regardless of the request method, POST or GET, it will have the form values. Works great for pages that use both methods. On top of that, it has cookie values as well. One stop shopping! Therein lies its tragic flaw. In the case of a name conflict, the cookie value will override any form values. Thus it is ridiculously easy for a bad actor to craft a counterfeit cookie on their browser, which will overwrite any form value you might be expecting from the request. $_REQUEST is an easy route for hackers to inject arbitrary data into your form values. To be extra safe, stick to the specific variables and avoid the one size fits all.

Since our AJAX exchange is for the plugin’s settings page, the user must be logged in. If you recall from the jQuery section, the action: value is "my_tag_count". This means our action hook tag will be wp_ajax_my_tag_count. If our AJAX exchange were to be utilized by users who were not currently logged in, the action hook tag would be wp_ajax_nopriv_my_tag_count The basic code used to hook the action looks like this:

1
2
3
4
5
add_action( 'wp_ajax_my_tag_count', 'my_ajax_handler' );
function my_ajax_handler() {
    // Handle the ajax request
    wp_die(); // All ajax handlers die when finished
}

The first thing your AJAX handler should do is verify the nonce sent by jQuery with check_ajax_referer(), which should be the same value that was localized when the script was enqueued.

1
check_ajax_referer( 'title_example' );

The provided parameter must be identical to the parameter provided earlier to wp_create_nonce(). The function simply dies if the nonce does not check out. If this were a true nonce, now that it was used, the value is no longer any good. You would then generate a new one and send it to the callback script so that it can be used for the next request. But since WordPress nonces are good for twenty-four hours, you needn’t do anything but check it.

Top ↑

Data #Data

With the nonce out of the way, our handler can deal with the data sent by the jQuery script contained in $_POST['title']. We can save the user’s selection in user meta by using update_user_meta().

1
update_user_meta( get_current_user_id(), 'title_preference', $_POST['title']);

Then we build a query in order to get the post count for the selected title tag.

1
2
3
4
$args = array(
    'tag' => $_POST['title'],
);
$the_query = new WP_Query( $args );

Finally we can send the response back to the jQuery script. There’s several ways to transmit data. Let’s look at some of the options before we deal with the specifics of our example.

Top ↑

XML #XML

PHP support for XML leaves something to be desired. Fortunately, WordPress provides the WP_Ajax_Response class to make the task easier. The WP_Ajax_Response class will generate an XML-formatted response, set the correct content type for the header, output the response xml, then die — ensuring a proper XML response.

Top ↑

JSON #JSON

This format is lightweight and easy to use, and WordPress provides the wp_send_json function to json-encode your response, print it, and die — effectively replacing WP_Ajax_Response. WordPress also provides the wp_send_json_success and wp_send_json_error functions, which allow the appropriate done() or fail() callbacks to fire in JS.

Top ↑

Other #Other

You can transfer data any way you like, as long as the sender and receiver are coordinated. Text formats like comma delimited or tab delimited are one of many possibilities. For small amounts of data, sending the raw stream may be adequate. That is what we will do with our example – we will send the actual replacement HTML, nothing else.

1
echo $_POST['title'].' ('.$the_query->post_count.') ';

In a real world application, you must account for the possibility that the action could fail for some reason–for instance, maybe the database server is down. The response should allow for this contingency, and the jQuery script receiving the response should act accordingly, perhaps telling the user to try again later.

Top ↑

Die #Die

When the handler has finished all of its tasks, it needs to die. If you are using the WP_Ajax_Response or wp_send_json* functions, this is automatically handled for you. If not, simply use the WordPress wp_die() function.

1
2
wp_die();
// That's all folks!

Top ↑

AJAX Handler Summary #AJAX Handler Summary

The complete AJAX handler for our example looks like this:

1
2
3
4
5
6
7
8
9
10
//JSON
function my_ajax_handler() {
    check_ajax_referer( 'title_example' );
    update_user_meta( get_current_user_id(), 'title_preference', $_POST['title'] );
    $args = array(
        'tag' => $_POST['title'],
    );
    $the_query = new WP_Query( $args );
        wp_send_json( $_POST['title'] . ' (' . $the_query->post_count . ') ' );
}
1
2
3
4
5
6
7
8
9
10
11
//Other
function my_ajax_handler() {
    check_ajax_referer( 'title_example' );
    update_user_meta( get_current_user_id(), 'title_preference', $_POST['title'] );
    $args = array(
        'tag' => $_POST['title'],
    );
    $the_query = new WP_Query( $args );
    echo $_POST['title'].' ('.$the_query->post_count.') ';
    wp_die(); // All ajax handlers should die when finished
}

Heartbeat API

The Heartbeat API is a simple server polling API built in to WordPress, allowing near-real-time frontend updates.

How it works #How it works

When the page loads, the client-side heartbeat code sets up an interval (called the “tick”) to run every 15-60 seconds. When it runs, heartbeat gathers data to send via a jQuery event, then sends this to the server and waits for a response. On the server, an admin-ajax handler takes the passed data, prepares a response, filters the response, then returns the data in JSON format. The client receives this data and fires a final jQuery event to indicate the data has been received.

The basic process for custom Heartbeat events is:

  1. Add additional fields to the data to be sent (JS heartbeat-send event)
  2. Detect sent fields in PHP, and add additional response fields (heartbeat_received filter)
  3. Process returned data in JS (JS heartbeat-tick)

(You can choose to use only one or two of these events, depending on what functionality you need.)

Top ↑

Using the API #Using the API

Using the heartbeat API requires two separate pieces of functionality: send and receive callbacks in JavaScript, and a server-side filter to process passed data in PHP.

Sending Data to the Server #Sending Data to the Server

When Heartbeat sends data to the server, you can include custom data. This can be any data you want to send to the server, or a simple true value to indicate you are expecting data.

1
2
3
4
jQuery( document ).on( 'heartbeat-send', function ( event, data ) {
    // Add additional data to Heartbeat data.
    data.myplugin_customfield = 'some_data';
});

Top ↑

Receiving and Responding on the Server #Receiving and Responding on the Server

On the server side, you can then detect this data, and add additional data to the response.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Add filter to receive hook, and specify we need 2 parameters.
add_filter( 'heartbeat_received', 'myplugin_receive_heartbeat', 10, 2 );
/**
 * Receive Heartbeat data and respond.
 *
 * Processes data received via a Heartbeat request, and returns additional data to pass back to the front end.
 *
 * @param array $response Heartbeat response data to pass back to front end.
 * @param array $data Data received from the front end (unslashed).
 */
function myplugin_receive_heartbeat( $response, $data ) {
    // If we didn't receive our data, don't send any back.
    if ( empty( $data['myplugin_customfield'] ) ) {
        return $response;
    }
    // Calculate our data and pass it back. For this example, we'll hash it.
    $received_data = $data['myplugin_customfield'];
    $response['myplugin_customfield_hashed'] = sha1( $received_data );
    return $response;
}

Top ↑

Processing the Response #Processing the Response

Back on the frontend, you can then handle receiving this data back.

1
2
3
4
5
6
7
8
jQuery( document ).on( 'heartbeat-tick', function ( event, data ) {
    // Check for our data, and use it.
    if ( ! data.myplugin_customfield_hashed ) {
        return;
    }
    alert( 'The hash is ' + data.myplugin_customfield_hashed );
});

Not every feature will need all three of these steps. For example, if you don’t need to send any data to the server, you can use just the latter two steps.

Summary

Here are all the example code snippets from the preceding discussion, assembled into two complete code pages: one for jQuery and the other for PHP.

PHP #PHP

This code resides on one of your plugin pages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php add_action('admin_enqueue_scripts', 'my_enqueue');
functionmy_enqueue($hook) {
    if( 'myplugin_settings.php'!= $hook) return;
    wp_enqueue_script( 'ajax-script',
        plugins_url( '/js/myjquery.js', __FILE__),
        array('jquery')
    );
    $title_nonce= wp_create_nonce('title_example');
    wp_localize_script('ajax-script', 'my_ajax_obj', array(
        'ajax_url'=> admin_url( 'admin-ajax.php'),
        'nonce'=> $title_nonce,
    ));
}
add_action('wp_ajax_my_tag_count', 'my_ajax_handler');
functionmy_ajax_handler() {
    check_ajax_referer('title_example');
    update_user_meta( get_current_user_id(), 'title_preference', $_POST['title']);
    $args= array(
       'tag'=> $_POST['title'],
    );
    $the_query= newWP_Query( $args);
    echo$_POST['title'].' ('.$the_query->post_count.') ';
    wp_die(); // all ajax handlers should die when finished
}

Top ↑

jQuery #jQuery

This code is in the file js/myjquery.js below your plugin folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
jQuery(document).ready(function($) {       //wrapper
    $(".pref").change(function() {         //event
        varthis2 = this;                  //use in callback
        $.post(my_ajax_obj.ajax_url, {     //POST request
           _ajax_nonce: my_ajax_obj.nonce, //nonce
            action: "my_tag_count",        //action
            title: this.value              //data
        }, function(data) {                //callback
            this2.nextSibling.remove();    //remove the current title
            $(this2).after(data);          //insert server response
        });
    });
});

And after storing the preference, the resulting post count is added to the selected title.

More Information #More Information

Cron

What is WP-Cron #What is WP-Cron

Cron is the time-based task scheduling system that is available on UNIX systems. WP-Cron is how WordPress handles scheduling time-based tasks in WordPress. Several WordPress core features, such as checking for updates and publishing scheduled post, utilize WP-Cron.

WP-Cron works by: on every page load, a list of scheduled tasks is checked to see what needs to be run. Any tasks scheduled to be run will be run during that page load. WP-Cron does not run constantly as the system cron does; it is only triggered on page load. Scheduling errors could occur if you schedule a task for 2:00PM and no page loads occur until 5:00PM.

Top ↑

Why use WP-Cron #Why use WP-Cron

Why use WP-Cron? Many hosting services are shared and do not provide access to the system cron but WordPress core and many plugins do need a cron system to perform time based tasks. Cron is a useful tool, thus the beginnings of WP-Cron. Though it may not run at a specific time, WP-Cron will get your tasks done in a timely manner. Using the WordPress API is a simpler method to setting up cron tasks than going outside of WordPress.

With a system cron if the time passes and the task did not run it is lost and will never run. WP-Cron will run tasks regardless of how old they are. Tasks will sit in a queue until a page is loaded to trigger them, thus no task will ever be lost.

Understanding WP-Cron Scheduling

Unlike a traditional system cron that schedules tasks for specific times (i.e. every hour at 5 minutes past the hour), WP-Cron uses intervals to simulate a system cron. WP-Cron is given the time for the first task and an interval, the time in seconds, after which to repeat the task. For example, if you schedule a task to begin at 2:00PM with an interval of 300 seconds (five minutes), the task would first run at 2:00PM and then again at 2:05PM and every five minutes thereafter.

To simplify scheduling tasks, WordPress offers three default intervals and an easy method for adding custom intervals.

The default intervals provided by WordPress are:

  • hourly
  • twicedaily
  • daily

To add a custom interval, you can create a filter, such as:

1
2
3
4
5
6
7
8
9
10
add_filter( 'cron_schedules', 'example_add_cron_interval' );
function example_add_cron_interval( $schedules ) {
    $schedules['five_seconds'] = array(
        'interval' => 5,
        'display'  => esc_html__( 'Every Five Seconds' ),
    );
    return $schedules;
}

This filter function creates a new interval that will allow us to run a cron task every five seconds.

Note: All intervals are in seconds.

Scheduling WP Cron Events

Scheduling a recurring task #Scheduling a recurring task

In order to get your task to execute you must create your own custom hook and give that hook the name of a function to execute. This is a very important step. Forget it and your task will never run.

The following will create the hook. The first parameter is the name of the hook, and the second is the name of your function to call.

1
add_action( 'bl_cron_hook', 'bl_cron_exec' );

Now on to the actual scheduling of the task. Another important note is that WP-Cron is kind of naive when scheduling tasks. Tasks are driven by the hook provided for the task, however if you call wp_schedule_event() multiple times, even with the same hook name, the event will be scheduled multiple times. If your code adds the task on each page load this could result in the task being scheduled several thousand times. Probably not a great idea. WordPress provides a convenient function called wp_next_scheduled() to check if a particular hook is already scheduled.

wp_next_scheduled() takes one parameter, the name of the hook. It will return either a string containing the timestamp of the next execution or false, signifying the task is not scheduled. It is used like so:

1
wp_next_scheduled( 'bl_cron_hook' )

Scheduling a recurring task is accomplished with wp_schedule_event(). This function takes three required parameters, and one additional parameter that is an array that can be passed to the function executing the wp-cron task. We will focus on the first three parameters. The parameters are as follows:

  1. $timestamp – The UNIX timestamp of the first time this task should execute
  2. $recurrence – The name of the interval in which the task will recur in seconds
  3. $hook – The name of our custom hook to call

We will use the 5 second interval and the hook we created earlier like so:

1
wp_schedule_event( time(), '5seconds', 'bl_cron_hook' );

Remember, we need to first ensure the task is not already scheduled, the full code for that is the following:

1
2
3
if ( ! wp_next_scheduled( 'bl_cron_hook' ) ) {
    wp_schedule_event( time(), '5seconds', 'bl_cron_hook' );
}

Top ↑

Unscheduling tasks #Unscheduling tasks

When you no longer need a task scheduled you can unschedule tasks with wp_unschedule_event(). This function takes the following two parameters:

  1. $timestamp – Timestamp of the next occurrence of the task
  2. $hook – Name of the custom hook to be called

This function will not only unschedule the task indicated by the timestamp, it will also unschedule all future occurrences of the task. Since you probably will not know the timestamp for the next task there is function, wp_next_schedule() that will find it for you. wp_next_scheduled() takes one parameter (that we care about):

  1. $hook – The name of the hook that is called to execute the task

Put it all together and the code looks like:

1
2
$timestamp = wp_next_scheduled( 'bl_cron_hook' );
wp_unschedule_event( $timestamp, 'bl_cron_hook' );

It is very important to unschedule tasks when you no longer need them as WordPress will continue to attempt to execute the tasks, even though they are no longer in use. An important place to remember to unschedule your tasks is upon plugin deactivation. Unfortunately there are many plugins in the WordPress.org Plugin Directory that do not clean up after themselves. If you find one of these plugins please let the author know to update their code. WordPress provides a function called register_deactivation_hook() that allows developers to run a function when their plugin is deactivated. It is very simple to setup and looks like:

1
2
3
4
5
6
register_deactivation_hook( __FILE__, 'bl_deactivate' );
function bl_deactivate() {
   $timestamp = wp_next_scheduled( 'bl_cron_hook' );
   wp_unschedule_event( $timestamp, 'bl_cron_hook' );
}

Hooking WP-Cron Into the System Task Scheduler

As previously mentioned, WP-Cron does not run continuously, which can be an issue if there are critical tasks that must run on time. There is an easy solution for this. Simply set up your system’s task scheduler to run on the intervals you desire (or at the specific time needed). The easiest solution is to use a tool to make a web request to the wp-cron.php file.

After scheduling the task on your system, there is one more step to complete. WordPress will continue to run WP-Cron on each page load. This is no longer necessary and will contribute to extra resource usage on your server. WP-Cron can be disabled in the wp-config.php file. Open the wp-config.php file for editing and add the following line:

1
define('DISABLE_WP_CRON', true);

Windows #Windows

Windows calls their time based scheduling system the Task Scheduler. It can be accessed via the Administrative Tools in the control panel.

How you setup the task varies with server setup. One method is to use PowerShell and a Basic Task. After creating a Basic Task the following command can be used to call the WordPress Cron script.

1
powershell "Invoke-WebRequest http://YOUR_SITE_URL/wp-cron.php"

Top ↑

Mac OS X and Linux #Mac OS X and Linux

Mac OS X and Linux both use cron as their time based scheduling system. It is typically access from the terminal with the

1
crontab -e

command. It should be noted that tasks will be run as a regular user or as root depending on the system user running the command.

Cron has a specific syntax that needs to be followed and contains the following parts:

  • Minute
  • Hour
  • Day of month
  • Month
  • Day of week
  • Command to execute

plugin-wp-cron-cron-scheduling

If a command should be run regardless of one of the time sections an asterisk (*) should be used. For example if you wanted to run a command every 15 minutes regardless of the hour, day, or month it would look like:

1
15 * * * * command

Many servers have

1
wget

installed and this is an easy tool to call the WordPress Cron script.

1
wget http://YOUR_SITE_URL/wp-cron.php

A daily call to your site’s WordPress Cron that triggers at midnight every night could look similar to:

1
0 0 * * * wget http://YOUR_SITE_URL/wp-cron.php

Simple Testing of WP-Cron

In this tutorial we will be creating a plugin that runs a task every 5 seconds and displays a message. In order to test this we will load the wp-cron.php file directly in our browser and output data to the display, otherwise we would have to perform some other action, maybe in the database, as the output is typically not shown on the site. So let’s run through the initial steps to get this setup quickly.

Create the plugin file #Create the plugin file

In the wp-content/plugins folder create the folder ‘my-wp-cron-test’ and the file ‘my-wp-cron-test.php’. Obviously you can name it whatever you would like. This name is simply descriptive of our intended use.

Open the PHP file for editing and insert the following lines

1
2
3
4
<?php
/*
Plugin Name: My WP-Cron Test
*/

This text will setup the plugin for display and activation in your wp-admin Plugins menu. Be sure to enable the plugin.

Top ↑

Testing the code #Testing the code

Open your browser and point it to YOUR_SITE_URL/wp-cron.php

Top ↑

View all currently scheduled tasks #View all currently scheduled tasks

WordPress has an undocumented function, _get_cron_array, that returns an array of all currently scheduled tasks. We are going to use a crude but effective method to dump out all the tasks using var_dump. For ease of use place the following code in the plugin:

1
echo '<pre>'; print_r( _get_cron_array() ); echo '</pre>';

Into an easy to call function like:

1
2
3
function bl_print_tasks() {
    echo '<pre>'; print_r( _get_cron_array() ); echo '</pre>';
}

Internationalization

What is Internationalization? #What is Internationalization?

Internationalization is the process of developing your plugin so that it can easily be translated into other languages. Internationalization is often abbreviated as i18n (because there are 18 letters between the i and the n).

Top ↑

Why is Internationalization Important? #Why is Internationalization Important?

WordPress is used all over the world, in countries where English is not the main language. The strings in the WordPress plugins need to be coded in a special way so that can be easily translated into other languages. As a developer, you may not have an easy time providing localizations for all your users; however, any translator can successfully localize the plugin without needing to modify the source code itself.

Read further on “How to Internationalize your Plugin“.

Top ↑

Resources #Resources

Internationalization

What is Internationalization? #What is Internationalization?

Internationalization is the process of developing your plugin so that it can easily be translated into other languages. Internationalization is often abbreviated as i18n (because there are 18 letters between the i and the n).

Top ↑

Why is Internationalization Important? #Why is Internationalization Important?

WordPress is used all over the world, in countries where English is not the main language. The strings in the WordPress plugins need to be coded in a special way so that can be easily translated into other languages. As a developer, you may not have an easy time providing localizations for all your users; however, any translator can successfully localize the plugin without needing to modify the source code itself.

Read further on “How to Internationalize your Plugin“.

Top ↑

Resources #Resources

Localization

What is localization? #What is localization?

Localization describes the subsequent process of translating an internationalized plugin. Localization is abbreviated as l10n (because there are 10 letters between the l and the n.)

Top ↑

Localization files #Localization files

POT (Portable Object Template) files #POT (Portable Object Template) files

This file contains the original strings (in English) in your plugin. Here is an example POT file entry:

1
2
3
#: plugin-name.php:123
msgid "Page Title"
msgstr ""

Top ↑

PO (Portable Object) files #PO (Portable Object) files

Every translator will take the POT file and translate the msgstr sections in their own language. The result is a PO file with the same format as a POT, but with translations and some specific headers. There is one PO file per language.

Top ↑

MO (Machine Object) files #MO (Machine Object) files

From every translated PO file a MO file is built. These are machine-readable, binary files that the gettext functions actually use (they don’t care about .POT or .PO files), and are a “compiled” version of the PO file. The conversion is done using the msgfmt tool. In general, an application may use more than one large logical translatable module and a different MO file accordingly. A text domain is a handle of each module, which has a different MO file.

Top ↑

Generating POT file #Generating POT file

The POT file is the one you need to hand to translators, so that they can do their work. The POT and PO files can easily be interchangeably renamed to change file types without issues. It is a good idea to offer the POT file along with your plugin, so that translators won’t have to ask you specifically about it. There are a couple of ways to generate a POT file for your plugin:

Top ↑

Plugin Directory Admin Tools #Plugin Directory Admin Tools

If your plugin is hosted in the WordPress.org Plugin Directory, go to your “Admin” page there and then click on ‘Continue’ by the “Generate POT file” section.

Plugin Admin Area

Plugin Admin Area

Then simply click on Get POT to download the POT file.

Generate POT

Generate POT

Top ↑

WordPress i18n tools #WordPress i18n tools

If your plugin is not in the Directory, you can checkout the WordPress Trunkdirectory from SVN (see Using Subversion to learn about SVN). You need to checkout the whole trunk because the wordpress-i18n tools uses code from WordPress core to generate the POT. You need the gettext (GNU Internationalization utilities) package and PHP to be installed on your server/computer before you can run the below command.

Open the command line and change the directory to the language folder.

1
cd theme-name/languages

You can run the makepot.php script in command line like this:

1
php path/to/makepot.php wp-plugin path/to/your-plugin-directory

After it’s finished you should see the POT file in the current directory.

Top ↑

Poedit #Poedit

You can also use Poedit locally when translating. This is an open source tool for all major OS. The free Poedit default version supports manual scanning of all source code with Gettext functions. A pro version of it also features one-click scanning for WordPress plugins. After generating the po file you can rename the file to POT. If a mo was generated then you can delete that file as it is not needed. If you don’t have the pro version you can easily get the Blank POT and use that as the base of your POT file. Once you have placed the blank POT in the languages folder you can click “Update” in Poedit to update the POT file with your strings.

internationalization-localization-03

Top ↑

Grunt Tasks #Grunt Tasks

There are even some grunt tasks that you can use to create the POTs. grunt-wp-i18n & grunt-pot
To set it up you need to install node.js. It is a simple installation. Then you need to install grunt in the directory that you would like to use grunt in. This is done via command line. An example Grunt.js and package.json that you can place in the root of your plugin. You can the grunt tasks with a simple command in the command line.

Top ↑

#

Top ↑

Translate PO file #Translate PO file

There are multiple ways to translate a PO file.

You can use a text editor to enter the translation. In a text editor it will look like this.

1
2
3
#: plugin-name.php:123
msgid "Page Title"
msgstr ""

You enter the translation between the quotation marks. For the German translation it would look like this.

1
2
3
#: plugin-name.php:123
msgid "Page Title"
msgstr "Seitentitel"

You can also use Poedit when translating. This is an open source tool for all major OS. The free Poedit default version supports manual scanning of all source code with Gettext functions. A pro version of it also features one-click scanning for WordPress plugins and themes.

A third option is to use a online translation service. The general idea is that you upload the POT file and then you can give permission to users or translators to translate your plugin. This allows you to track the changes, always have the latest translation and reduce the translation being done twice.

François-Xavier Bénard is running WP-Translations, a community “where translators meet developers.” It is run on Transifex, you can submit projects for translation as a developer, or translate existing plugins and themes for your language. This is great as there are existing translators there.

Here are a few tools that can be used to translate PO files online:

You can even use WordPress plugins to do the translation.

The translated file is to be saved as my-plugin-{locale}.mo. The locale is the language code and/or country code you defined in the constant WPLANG in the file wp-config.php. For example, the locale for German is de_DE. From the code example above the text domain is ‘my-plugin’ therefore the German MO and PO files should be named my-plugin-de_DE.mo and my-plugin-de_DE.po. For more information about language and country codes, see Installing WordPress in Your Language.

Top ↑

Generate MO file #Generate MO file

Top ↑

Command line #Command line

A program msgfmt is used to create the MO file. msgfmt is part of Gettext package. Otherwise command line can be used. A typical msgfmt command looks like this:

Unix Operating Systems

1
msgfmt -o filename.mo filename.po

Windows Operating Systems

1
msgfmt -o filename.mo filename.po

If you have a lot of PO files to convert at once, you can run it as a batch. For example, using a bash command:

Unix Operating Systems

1
2
# Find PO files, process each with msgfmt and rename the result to MO
for file in `find . -name "*.po"` ; do msgfmt -o ${file/.po/.mo} $file ; done

Windows Operating Systems
For Windows you need to install Cygwin first.

Create a potomo.sh

1
2
3
#! /bin/sh
# Find PO files, process each with msgfmt and rename the result to MO
for file in `/usr/bin/find . -name '*.po'` ; do /usr/bin/msgfmt -o ${file/.po/.mo} $file ; done

You can run this command in the command line.

1
cd C:/path/to/language/folder/my-plugin/languages & C:/cygwin/bin/bash -c /cygdrive/c/path/to/script/directory/potomo.sh

Top ↑

Poedit #Poedit

msgfmt is also integrated in Poedit allowing you to use it to generate the MO file. There is a setting in the preferences where you can enable or disable it.

internationalization-localization-04

Top ↑

Grunt task #Grunt task

There is grunt-po2mo that will convert all of the files.

Top ↑

Tips for Good Translations #Tips for GoodTranslations

Top ↑

Don’t translate literally, translate organically #Don’t translate literally, translate organically

Being bi- or multi-lingual, you undoubtedly know that the languages you speak have different structures, rhythms, tones, and inflections. Translated messages don’t need to be structured the same way as the English ones: take the ideas that are presented and come up with a message that expresses the same thing in a natural way for the target language. It’s the difference between creating an equal message and an equivalent message: don’t replicate, replace. Even with more structural items in messages, you have creative license to adapt and change if you feel it will be more logical for, or better adapted to, your target audience.

Top ↑

Try to keep the same level of formality (or informality) #Try to keep the same level of formality (or informality)

Each message has a different level of formality or informality. Exactly what level of formality or informality to use for each message in your target language is something you’ll have to figure out on your own (or with your team), but WordPress messages (informational messages in particular) tend to have a politely informal tone in English. Try to accomplish the equivalent in the target language, within your cultural context.

Top ↑

Don’t use slang or audience-specific terms #Don’t use slang or audience-specific terms

Some amount of terminology is to be expected in a blog, but refrain from using colloquialisms that only the “in” crowd will get. If the uninitiated blogger were to install WordPress in your language, would they know what the term means? Words like pingback, trackback, and feed are exceptions to this rule; they’re terminology that are typically difficult to translate, and many translators choose to leave in English.

Top ↑

Read other software’s localizations in your language #Read other software’s localizations in your language

If you get stuck or need direction, try reading through the translations of other popular software tools to get a feel for what terms are commonly used, how formality is addressed, etc. Of course, WordPress has its own tone and feel, so keep that in mind when you’re reading other localizations, but feel free to dig up UI terms and the like to maintain consistency with other software in your language.

Top ↑

Using Localizations #Using Localizations

Place the localization files in the language folder, either in the plugin languagesfolder or as of WordPress 3.7 in the plugin languages folder normally under wp-content. The full path would be wp-content/languages/plugins/my-plugin-fr_FR.mo.

As of WordPress 4.0 you can change the language in the “General Settings”. If you do not see any option or the language that you want to switch to then do the following steps:

  • Define WPLANG inside of wp-config.php to your chosen language. For example, if you wanted to use french, you would have:
    1
    define ('WPLANG', 'fr_FR');
  • Go to wp-admin/options-general.php or “Settings” -> “General”
  • Select your language in “Site Language” dropdown
  • Go to wp-admin/update-core.php
  • Click “Update translations”, when available
  • Core translations files are downloaded, when available

Top ↑

Resources #Resources

 

How to Internationalize Your Plugin

In order to make a string translatable in your application you have to wrap the original strings in a call to one of a set of special functions.

Introduction to Gettext #Introduction to Gettext

WordPress uses the gettext libraries and tools for i18n. If you look online, you’ll see the _() function which refers to the native PHP gettext-compliant translation function. With WordPress you should use the __() WordPress defined PHP function. If you want to get a broader and deeper view of gettext, we recommend you read the gettext online manual.

Top ↑

Text Domains #Text Domains

It’s important to use a text domain to denote all text belonging to that plugin. The text domain is a unique identifier, which makes sure WordPress can distinguish between all loaded translations. This increases portability and plays better with already existing WordPress tools. The text domain must match the slug of the plugin. If your plugin is a single file called my-plugin.php or it is contained in a folder called my-plugin the domain name should be my-plugin. If your plugin is hosted on wordpress.org it must be the slug of your plugin URL (wordpress.org/plugins/<slug>).
The text domain name must use dashes and not underscores.

String example:

1
__( 'String (text to be internationalized)', 'text-domain' );

Change “text-domain” to the slug of your plugin.

Warning:Do not use variable names for the text domain portion of a gettext function.  Do not do this as a shortcut: __( ‘Translate me.’ , $text_domain );

The text domain also needs to be added to the plugin header. WordPress uses it to internationalize your plugin meta-data even when the plugin is disabled. The text domain should be same as the one used when loading the text domain.

Header example:

1
2
3
4
5
/*
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 */

Top ↑

Domain Path #Domain Path

The domain path is used so that WordPress knows where to find the translation when the plugin is disabled. Only useful if the translations are located in a separate language folder. For example, if .mo files are located in the languages folder within your plugin then Domain Path will be “/languages” and must be written with the first slash. Defaults to the languages folder of the plugin:

Header example:

1
2
3
4
5
6
/*
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 * Domain Path: /languages
 */

Top ↑

Basic strings #Basic strings

The most commonly used one is __(). It just returns the translation of its argument:

1
__( 'Blog Options', 'my-plugin' );

Another simple one is _e(), which outputs the translation of its argument. Instead of writing:

1
echo __( 'WordPress is the best!', 'my-plugin' );

you can use the shorter:

1
_e( 'WordPress is the best!', 'my-plugin' );

Top ↑

Variables #Variables

If you are using variables in strings like in the example below you would use placeholders.

1
echo 'Your city is $city.'

The solution is to use the printf family of functions. Especially helpful are printfand sprintf. Here is what the right solution looks like:

1
2
3
4
5
printf(
    /* translators: %s: Name of a city */
    __( 'Your city is %s.', 'my-plugin' ),
    $city
);

Notice that here the string for translation is just the template "Your city is %s.", which is the same both in the source and at run-time.

If you have more than one placeholder in a string, it is recommended that you use argument swapping. In this case, single quotes (') are mandatory : double quotes (") will tell php to interpret the $s as the s variable, which is not what we want.

1
2
3
4
5
6
printf(
    /* translators: 1: Name of a city 2: ZIP code */
    __( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ),
    $city,
    $zipcode
);

Here the zip code is being displayed after the city name. In some languages displaying the zip code and city in opposite order would be more appropriate. Using %s prefix in the above example, allows for such a case. A translation can thereby be written:

1
2
3
4
5
6
printf(
    /* translators: 1: Name of a city 2: ZIP code */
    __( 'Your zip code is %2$s, and your city is %1$s.', 'my-plugin' ),
    $city,
    $zipcode
);

Important! This code is incorrect.

1
2
// This is incorrect do not use.
_e( "Your city is $city.", 'my-plugin' );

The strings for translation are extracted from the sources, so the translators will get this phrase to translate: "Your city is $city.".

However in the application _e will be called with an argument like "Your city is London." and gettext won’t find a suitable translation of this one and will return its argument: "Your city is London.". Unfortunately, it isn’t translated correctly.

Top ↑

Plurals #Plurals

Basic Pluralization #Basic Pluralization

If you have string that changes when the number of items changes. In English you have "One comment" and "Two comments". In other languages you can have multiple plural forms. To handle this in WordPress you can use the _n() function.

1
2
3
4
5
6
7
8
9
printf(
    _n(
        '%s comment',
        '%s comments',
        get_comments_number(),
        'my-plugin'
    ),
    number_format_i18n( get_comments_number() )
);

_n() accepts 4 arguments:

  • singular – the singular form of the string (note that it can be used for numbers other than one in some languages, so '%s item' should be used instead of 'One item')
  • plural – the plural form of the string
  • count – the number of objects, which will determine whether the singular or the plural form should be returned (there are languages, which have far more than 2 forms)
  • text domain – the plugins text domain

The return value of the functions is the correct translated form, corresponding to the given count.

Top ↑

Pluralization done later #Pluralization done later

You first set the plural strings with _n_noop() or _nx_noop().

1
2
3
4
$comments_plural = _n_noop(
    '%s comment.',
    '%s comments.'
);

Then at a later point in the code you can use translate_nooped_plural() to load the strings.

1
2
3
4
5
6
7
8
printf(
    translate_nooped_plural(
        $comments_plural,
        get_comments_number(),
        'my-plugin'
    ),
    number_format_i18n( get_comments_number() )
);

Top ↑

Disambiguation by context #Disambiguation by context

Sometimes one term is used in several contexts and although it is one and the same word in English it has to be translated differently in other languages. For example the word Post can be used both as a verb "Click here to post your comment" and as a noun "Edit this post". In such cases the _x() or _ex()function should be used. It is similar to __() and _e(), but it has an additional argument — the context:

1
2
_x( 'Post', 'noun', 'my-plugin' );
_x( 'Post', 'verb', 'my-plugin' );

Using this method in both cases we will get the string Comment for the original version, but the translators will see two Comment strings for translation, each in the different contexts.

Note that similarly to __(), _x() has an echo version: _ex(). The previous example could be written as:

1
2
_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' );

Use whichever you feel enhances legibility and ease-of-coding.

Top ↑

Descriptions #Descriptions

So that translators know how to translate a string like __( 'g:i:s a' ) you can add a clarifying comment in the source code. It has to start with the words translators: and to be the last PHP comment before the gettext call. Here is an example:

1
2
/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );

It’s also used to explain placeholders in a string like _n_noop( '<strong>Version %1$s</strong> addressed %2$s bug.','<strong>Version %1$s</strong> addressed %2$s bugs.' ).

1
2
3
/* translators: 1: WordPress version number, 2: plural number of bugs. */
_n_noop( '<strong>Version %1$s</strong> addressed %2$s bug.',
         '<strong>Version %1$s</strong> addressed %2$s bugs.' );

Top ↑

Newline characters #Newline characters

Gettext doesn’t like \r (ASCII code: 13) in translatable strings, so please avoid it and use \n instead.

Top ↑

Empty strings #Empty strings

The empty string is reserved for internal Gettext usage and you must not try to internationalize the empty string. It also doesn’t make any sense, because the translators won’t see any context.

If you have a valid use-case to internationalize an empty string, add context to both help translators and be in peace with the Gettext system.

Top ↑

Handling JavaScript files #Handling JavaScript files

Use wp_localize_script() to add translated strings or other server-side data to a previously enqueued script.

Top ↑

Escaping strings #Escaping strings

It is good to escape all of your strings, this way the translators cannot run malicious code. There are a few escape functions that are integrated with internationalisation functions.

Top ↑

Localization functions #Localization functions

Top ↑

Basic functions #Basic functions

Top ↑

Translate & Escape functions #Translate & Escape functions

Strings that require translation and is used in attributes of html tags must be escaped.

Top ↑

Date and number functions #Date and number functions

Top ↑

Best Practices for writing strings #Best Practices for writing strings

Here are the best practices for writing strings

  • Use decent English style – minimize slang and abbreviations.
  • Use entire sentences – in most languages word order is different than that in English.
  • Split at paragraphs – merge related sentences, but do not include a whole page of text in one string.
  • Do not leave leading or trailing whitespace in a translatable phrase.
  • Assume strings can double in length when translated
  • Avoid unusual markup and unusual control characters – do not include tags that surround your text
  • Do not put unnecessary HTML markup into the translated string
  • Do not leave URLs for translation, unless they could have a version in another language.
  • Add the variables as placeholders to the string as in some languages the placeholders change position.
1
2
3
4
printf(
    __( 'Search results for: %s', 'my-plugin' ),
    get_search_query()
);
  • Use format strings instead of string concatenation – translate phrases and not words –
    1
    2
    3
    4
    5
    printf(
        __( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ),
        $city,
        $zipcode
    );

    is always better than:

    1
    __( 'Your city is ', 'my-plugin' ) . $city . __( ', and your zip code is ', 'my-plugin' ) . $zipcode;
  • Try to use the same words and same symbols so not multiple strings needs to be translated e.g.__( 'Posts:', 'my-plugin' ); and __( 'Posts', 'my-plugin' );

Top ↑

Add Text Domain to strings #Add Text Domain to strings

You must add your Text domain as an argument to every __(), _e() and __n()gettext call, or your translations won’t work.

Examples:

  • 1
    __( 'Post' )

    should become

    1
    __( 'Post', 'my-theme' )
  • 1
    _e( 'Post' )

    should become

    1
    _e( 'Post', 'my-theme' )
  • 1
    _n( 'One post', '%s posts', $count )

    should become

    1
    _n( 'One post', '%s posts', $count, 'my-theme' )

If there are strings in your plugin that are also used in WordPress core (e.g. ‘Settings’), you should still add your own text domain to them, otherwise they’ll become untranslated if the core string changes (which happens).

Adding the text domain by hand can be a burden if not done continuously when writing code and that’s why you can do it automatically:

  • If your plugin is in the WordPress.org Plugin Directory, go to your “Admin” page there and scroll to “Add Domain to Gettext Calls”.
  • Upload the file for which you want the text domain to be added.
  • The click on “Get domainified file”.

WordPress.org Plugin Admin area

WordPress.org Plugin Admin area

Otherwise:

  • Download the add-textdomain.php script to the folder where the file is you want to add the text domain
  • In command line move to the directory where the file is
  • Run this command to create a new file with the text domain added
1
php add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
  • If you wish to have the add-textdomain.php in a different folder you just need to define the location in the command.
1
php \path\to\add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
  • Use this command if you don’t want a new file outputted.
1
php add-textdomain.php -i my-plugin my-plugin.php
  • If you want to change multiple files in a directory you can also pass a directory to the script.
1
php add-textdomain.php -i my-plugin my-plugin-directory

After it’s done, the text domain will be added to the end of all gettext calls in the file. If there is an existing text domain it will not be replaced.

Top ↑

Loading Text Domain #Loading Text Domain

Note:After WordPress 4.6 came out, translations now take translate.wordpress.org as priority and so plugins that are translated via translate.wordpress.org do not necessary require load_plugin_textdomain() anymore.
If you don’t want to add a load_plugin_textdomain() call to your plugin you have to set the Requires at least: field in your readme.txt to 4.6.

You need to load the MO file with your plugin’s translations. You can load them by calling the function load_plugin_textdomain(). This call loads {text-domain}-{locale}.mo from your plugin’s base directory. The locale is the language code and/or country code of the site language setting under General Settings. For more information about language and country codes, see WordPress in Your Language.

From the code example above the text domain is my-plugin therefore the German MO and PO files should be named my-plugin-de_DE.mo and my-plugin-de_DE.po.
Example:

1
2
3
4
function my_plugin_load_plugin_textdomain() {
    load_plugin_textdomain( 'my-plugin', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' );
}
add_action( 'plugins_loaded', 'my_plugin_load_plugin_textdomain' );

Top ↑

Language Packs #Language Packs

If you’re interested in language packs and how the import to translate.wordpress.org is working, please read the Meta Handbook page aboutTranslations.

Internationalization Security

Security is often overlooked when talking about internationalization, but there are a few important things to keep in mind.

Check for Spam and Other Malicious Strings #Check for Spam and Other Malicious Strings

When a translator submits a localization to you, always check to make sure they didn’t include spam or other malicious words in their translation. You can use Google Translate to translate their translation back into your native language so that you can easily compare the original and translated strings.

Top ↑

Escape Internationalized Strings #Escape Internationalized Strings

You can’t trust that a translator will only add benign text to their localization; if they want to, they could add malicious JavaScript or other code instead. To protect against that, it’s important to treat internationalized strings like you would any other untrusted input.

If you’re outputting the strings, then they should be escaped.

Insecure:

1
<?php _e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>

Secure:

1
<?php esc_html_e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>

Alternatively, some people choose to rely on a translation verification mechanism, rather than adding escaping to their code. One example of a verification mechanism is the editor roles that the WordPress Polyglots team uses for translate.wordpress.org. This ensures that any translation submitted by an untrusted contributor has been verified by a trusted editor before being accepted.

Top ↑

Use Placeholders for URLs #Use Placeholders for URLs

Don’t include URLs in internationalized strings, because a malicious translator could change them to point to a different URL. Instead, use placeholders for printf() or sprintf().

Insecure:

1
2
3
4
5
<?php _e(
     register for a WordPress.org account</a>.',
    'your-text-domain'
); ?>

Secure:

1
2
3
4
5
6
7
<?php printf(
    __(
        'Please <a href="%s">register for a WordPress.org account</a>.',
        'your-text-domain'
    ),
); ?>

Top ↑

Compile Your Own .mo Binaries #Compile Your Own .mo Binaries

Often translators will send the compiled .mo file along with the plaintext .po file, but you should discard their .mo file and compile your own, because you have no way of knowing whether or not it was compiled from the corresponding .po file, or a different one. If it was compiled against a different one, then it could contain spam and other malicious strings without your knowledge.

Using PoEdit to generate the binary will override the headers in the .po file, so instead it’s better to compile it from the command line:

1
msgfmt -cv -o /path/to/output.mo /path/to/input.po

WordPress.org

If you want us to host your plugin on WordPress.org, Just ask us to host it for you.

If you’re just getting started, please think about how to plan your plugin first.

Features #Features

All plugins hosted on WordPress.org have access to:

  1. Monitor downloads
  2. Get version statistics
  3. Receive feedback and reviews from users
  4. Provide support via a free forum

We also have a WordPress.org Plugin API that can be used to monitor statistics of plugins.

Top ↑

Requirements #Requirements

We have a large list of Detailed Plugin Guidelines with detailed examples and explanations. What follows is a brief overview of the most important aspects:

  1. Your plugin must be compatible with the GNU General Public License v2 or later. We strongly recommend using the same license as WordPress — “GPLv2 or later.”
  2. If you do not specify a compatible license, what you check in is considered GPLv2 or later.
  3. The plugin and developer must not do anything illegal, dishonest, or morally offensive.
  4. The provided Subversion repository must be used for functional WordPress plugins only. The Directory is a hosting site, not a listing site.
  5. The plugin must not embed external links on the public site (like a “powered by” link or advertisting) without explicit user permission.

Top ↑

Getting Plugins Hosted #Getting Plugins Hosted

To have your plugin hosted on WordPress.org, please follow these steps:

  1. Register on WordPress.org with a valid, regularly checked email address. If you are submitting a plugin on behalf of a company, use an official company email for verification.
  2. Whitelist plugins@wordpress.org in your email client to ensure you receive email communications.
  3. Submit your plugin with a brief overview of what it does and a link to a complete, ready to go zip of the plugin.

Within 14 business days, your plugin will be manually reviewed by a member of the WordPress Plugin Review team. You may be emailed and asked to provide more information or correct issues. Once approved, you’ll receive an email with details as to how to access to a Subversion Repository where you’ll store your plugin.

After you upload your plugin (and a readme file) in that repository via SVN, it will appear on the plugin directory.

Top ↑

Additional Reading #Additional Reading

Detailed Plugin Guidelines

Tip:Last Updated: January 30, 2017

The goal of the WordPress Plugin Directory is to provide a safe place for all WordPress users – from the non-technical to the developer – to download plugins that are consistent with the goals of the WordPress project.

To that end, we want to ensure a simple and transparent process for developers to submit plugins for the directory. As part of our ongoing efforts to make the plugin directory inclusion process more transparent, we have created a list of developer guidelines. We strive to create a level playing field for all developers.

If you have suggestions to improve the documentation, or questions about it, please email us at plugins@wordpress.org and let us know.

Plugin Submissions #Plugin Submissions

In order to submit a plugin, there are three steps:

  1. Register on WordPress.org with a valid, regularly checked email address. If you are submitting a plugin on behalf of a company, use an official company email for verification.
  2. Whitelist plugins@wordpress.org in your email client to ensure you receive email communications.
  3. Submit your plugin with a brief overview of what it does and a link to a complete, ready to go zip of the plugin.

Once a plugin is queued for review, we will review the plugin for any issues. Most of the issues can be avoided by following the guidelines below. If we do find issues, we will contact the developer(s), and work towards a resolution.

Top ↑

Developer Expectations #Developer Expectations

Developers, all users with commit access, and all users who officially support a plugin are expected to abide by the Directory Guidelines. Violations may result in plugins or plugin data (for previously approved plugins) being removed from the directory until the issues are resolved. Plugin data, such as user reviews, may not be restored, depending on the nature of the violation and the results of a peer-review of the situation. Repeat violations may result in all the author’s plugins being removed and the developer being banned from hosting plugins on WordPress.org. It is the responsibility of the plugin developer to ensure their contact information on WordPress.org is up to date and accurate, in order that they receive all notifications from the plugins team.

All code in the directory should be made as secure as possible. Security is the ultimate responsibility of the plugin developer, however the Plugin Directory enforces this to the best of our ability. Should a plugin be found to have security issues, it will be closed until the situation is resolved. In extreme cases, the plugin may be updated by the WordPress Security team and propagated for the safety of the general public.

While we attempt to account for as many relevant interpretations of the guidelines as possible, it is unreasonable to expect that every circumstance will be explicitly covered. If you are uncertain whether a plugin might violate the guidelines, please contact us at plugins@wordpress.org and ask.

Top ↑

The Guidelines #The Guidelines

1. Plugins must be compatible with the GNU General Public License v2, or any later version, to be hosted on WordPress.org.

Though any GPL-compatible license is acceptable, using the same license as WordPress — “GPLv2 or later” — is recommended. All code, data, and images — anything stored in the directory — must comply with the GPL (any version, two or later), regardless of their creator. Included third-party libraries must also be compatible. For a specific list of compatible licenses, please read the GPL-Compatible license list on gnu.org.

2. Plugin developers are responsible for the files they upload and services they utilize.

It is the sole responsibility of the plugin developer to ensure all files within their plugins comply with the guidelines. They are expected to confirm, before uploading to SVN, the licensing of all included files, from original source code to images and libraries. In addition, they must comply to the terms of use for all third party services and APIs utilized by their plugins. If there is no way to validate the licensing of a library or the terms of an API, then they cannot be included.

3. A stable version of your plugin must be available from its WordPress Plugin Directory page.

The only version of the plugin that WordPress.org distributes is the one in the directory. Though you may develop your code somewhere else, please remember that users will be downloading from the directory, not your development environment.

4. Keep your code (mostly) human readable.

Intentionally obscuring code by hiding it with techniques or systems similar to p,a,c,k,e,r‘s obfuscate feature, uglify’s mangle, or unclear naming conventions such as $z12sdf813d, is not permitted in the directory. Unfortunately, many people use such methods to try and hide malicious code, such as backdoors or tracking. In addition, WordPress code is intended for anyone to be able to learn from, edit, and adapt. Making code non-human readable forces future developers to face an unnecessary hurdle. Minified code may be used, however the unminified versions should be included whenever possible. We recommend following WordPress Core Coding Standards.

5. Trialware is not allowed in the directory.

Attempting to upsell the user on other products and features is acceptable within limits.

  • Upsell notifications should not be overly prominent or annoying.
  • Plugins may not contain functionality that is crippled or locked, only to be unlockable by payment or upgrade. Paid functionality must be part of an externally hosted service or a separate plugin, that is not hosted on wordpress.org.
  • Plugins may not disable included functionality after a trial period or quota.

6. Software as a Service is permitted in the directory.

Plugins that act as an interface to some external third party service (e.g. a video hosting site) are allowed, even for paid services. The service itself must provide functionality of substance and be clearly documented in the readme file submitted with the plugin, preferably with a link to the service’s Terms of Use.

Services and functionality not allowed include:

  • A service that exists for the sole purpose of validating licenses or keys while all functional aspects of the plugin are included locally is not permitted.
  • Creation of a service by moving arbitrary code out of the plugin so that the service may falsely appear to provide supplemented functionality is prohibited.
  • Storefronts that are not services. A plugin that acts only as a front-end for products to be purchased from external systems will not be accepted.

7. The plugin may not “phone home” or track users without their informed, explicit, opt-in consent.

In the interest of protecting user privacy, plugins may not contact external servers without the explicit consent of the user via requiring registration with a service or a checkbox within the settings. This method is called ‘opt in.’ Documentation on how any user data is collected, and used, should be included in the plugin’s readme, preferably with a clearly stated privacy policy.

This restriction includes the following:

  • No unauthorized collection of user data. Users may be asked to submit information but it cannot be automatically recorded without explicit confirmation from the user.
  • Intentionally misleading users into submitting information as a requirement for use of the plugin itself is prohibited.
  • Images and scripts should be loaded locally as part of the plugin whenever possible. If external data (such as blocklists) is required, their inclusion must be made clear to the user.
  • Any third party advertisement mechanisms used within the plugin must have all tracking features disabled by default. Advertisement mechanisms which do not have the capability of disabling user tracking features are prohibited.

The sole exception to this policy is Software as a Service, such as Twitter, an Amazon CDN plugin, or Akismet. By installing, activating, registering, and configuring plugins that utilize those services, consent is granted for those systems.

8. The plugin may not send executable code via third-party systems.

Externally loading code from documented services is permitted, however all communication must be made as securely as possible. Executing outside code within a plugin when not acting as a service is not allowed, for example:

  • Serving updates or otherwise installing plugins, themes, or add-ons from servers other than WordPress.org’s
  • Installing premium versions of the same plugin
  • Calling third party CDNs for reasons other than font inclusions; all non-service related JavaScript and CSS must be included locally
  • Using third party services to manage regularly updated lists of data, when not explicitly permitted in the service’s terms of use
  • Using iframes to connect admin pages; APIs should be used to minimize security risks

9. The plugin and its developers must not do anything illegal, dishonest, or morally offensive.

While this is subjective and rather broad, the intent is to prevent plugins, developers, and companies from abusing the freedoms and rights of end users as well as other plugin developers.

This includes (but is not restricted to) the following examples:

  • Artificially manipulating search results via keyword stuffing, black hat SEO, or otherwise within the readme
  • Offering to drive more traffic to sites that use the plugin
  • Compensating, misleading, pressuring, extorting, or blackmailing users for reviews
  • Implying users must pay to unlock included features
  • Creating accounts to generate fake reviews or support tickets (i.e. sockpuppeting)
  • Falsifying personal information to intentionally disguise identities and avoid sanctions for previous infractions
  • Taking other developers’ plugins and presenting them as original work
  • Utilizing the user’s server or resources as part of a botnet
  • Intentionally attempting to exploit loopholes in the guidelines
  • Violations of the WordCamp code of conduct

10. The plugin must not embed external links or credits on the public site without explicitly asking the user’s permission.

All “Powered By” or credit displays and links included in the plugin code must be optional and default to not show on users’ front-facing websites. Users must opt-in to displaying any and all credits and links via clearly stated and understandable choices, not buried in the terms of use or documentation. Plugins may not require credit or links be displayed in order to function. Services are permitted to brand their output as they see fit, provided the code is handled in the service and not the plugin.

11. The plugin should not hijack the admin dashboard.

Users prefer and expect plugins to feel like part of WordPress. Constant nags and overwhelming the admin dashboard with unnecessary alerts detract from this experience.

Upgrade prompts, notices, and alerts should be limited in scope and used sparingly or only on the plugin’s setting page. Any site wide notices or embedded dashboard widgets must be dismissible. Error messages and alerts should include information on how to resolve the situation, and remove themselves when completed.

Advertising within the WordPress Dashboard should be avoided. While developers are permitted to promote their own products and services, historically they have been ineffective; ideally users rarely visit these screens. Remember: tracking referrals via those ads is not permitted (see guideline 7) and most third-party systems do not permit back-end advertisements (notably, Google). Abusing the guidelines of an advertising system will result in such actions being reported.

Developers are welcome and encouraged to include links to their own sites or social networks, as well as locally (within the plugin) including images to enhance that experience.

12. Public facing pages on WordPress.org (readmes) may not spam.

Public facing pages, including readmes and translation files, may not be used to spam. Spammy behavior includes (but is not limited to) unnecessary affiliate links, tags to competitors plugins, use of over 12 tags total, blackhat SEO, and keyword stuffing.

Links to directly required products, such as themes or other plugins required for the plugin’s use, are permitted within moderation. Similarly, related products may be used in tags but not competitors. If a plugin is a WooCommerce extension, it may use the tag ‘woocommerce.’ However if the plugin is an alternative to Akismet, it may not use that term as a tag. Repetitive use of a tag or specific term is considered to be keyword stuffing, and is not permitted.

Write your readmes for people, not bots.

In all cases, affiliate links must be disclosed and must directly link to the affiliate service, not a redirect or cloaked URL.

13. The plugin should make use of WordPress’ default libraries.

WordPress includes a number of useful libraries, such as jQuery, Atom Lib, SimplePie, PHPMailer, PHPass, and more. For security and stability reasons, plugins may not include those libraries in their own code, but instead must use the versions of those libraries packaged with WordPress.

For a list of all javascript libraries included in WordPress, please review Default Scripts Included and Registered by WordPress.

14. Frequent commits to a plugin should be avoided.

The SVN repository is a release repository, not a development one. All commits will trigger a regeneration of the zip files associated with the plugin, so only code that is ready for deployment (be that a stable release, beta, or RC) should be pushed to SVN. Including a descriptive and informative message with each commit is strongly recommended. Frequent ‘trash’ commit messages like ‘update’ or ‘cleanup’ makes it hard for others to follow changes. Commits that only tweak minor aspects of the plugin (including the readme) cause undue strain on the system and can be seen as gaming Recently Updated lists.

15. The plugin version number must increment every time a new version is released.

Users will only be alerted to updates when the code version is incremented in SVN. Developers can deploy these updates either by incrementing the plugin version number in the readme.txt in the trunk branch or by creating a new tag branch with a readme.txt which has an incremented plugin version matching the branch directory name.

If a developer employs the tag directories approach to distribute the latest version of their plugin, the trunk folder can be continually updated without version number changes. The tag directories should generally not be updated past the initial tagging unless the readme.txt needs to be updated to support the release of a new version of WordPress.

For more information on tagging, please read our SVN directions on tagging and how the readme.txt works.

16. A complete plugin must be available at the time of submitting the plugin request to the directory.

All plugins are examined prior to approval, which is why a link to a zip is required. Names cannot be “reserved” for future use. Directory names for approved plugins that are not used within a reasonable amount of time may be given to other developers.

17. Respect trademarks and projects.

The use of trademarks or other projects as the sole or initial term of a plugin slug is prohibited unless proof of legal ownership/representation can be confirmed. For example, the WordPress Foundation has trademarked the term “WordPress”and it is a violation to use “wordpress” in a domain name. This policy extends to plugin slugs.

As another example, only employees of Facebook should use the slug “Facebook,” or their brand in a context such as “Facebook Dancing Sloths.” Non-employees should use a format such as “Dancing Sloths for Facebook” instead to avoid potentially misleading users into believing the plugin was developed by Facebook. Similarly, if you don’t represent the “Chart.js” project, it’s inappropriate to use that as the name of your plugin.

Original branding is recommended as it not only helps to avoid confusion, but is more memorable to the user.

18. We reserve the right to alter the Plugin Guidelines at any time with or without notice.

We reserve the right to update these guidelines at any time as we feel necessary.

We reserve the right to arbitrarily disable or remove any plugin from the directory for any reason whatsoever, even for reasons not explicitly covered by these guidelines. Our intent is to enforce these guidelines with as much fairness as humanly possible to ensure plugins’ quality and the safety of their users.

Planning Your Plugin

You’ve written the next Hello Dolly and you want the world to use it. What should you do?

1. Test once and test again #1. Test once and test again

With any luck, your plugin will be used by lots of people in many different situations and hosting environments. You’ll want to make sure you’ve tested your plugin to make sure it works in any situation and doesn’t frustrate your users.

Top ↑

2. Pick a good name #2. Pick a good name

A plugin name should reflect the uniqueness of you and your work. When you pick a name, make sure you’re not violating trademarks or stomping on someone else’s product names. If you’re not working for Facebook, you shouldn’t name your plugin ‘Facebook’s Dancing Squirrels’ after all. A much better name would be ‘Dancing Squirrels for Facebook’ for example. It can be hard to come up with a good name, so take your time. Your plugin URL cannot be changed after you submit it, but the display name can change a thousand times.

Top ↑

3. Write great documentation #3. Write great documentation

A README.txt file is the best place to start, as it’s a standard reference point for all plugins. You’ll want to make sure you include:

  • A concise description of what your plugin actually does. If it does a lot, it might be better as two plugins.
  • Installation instructions, especially if there’s special configuration to be done. If a user needs to register with your service, make sure you link to it.
  • Directions on how to get support, and what you do and do not support.

Top ↑

4. Push out a first version to WordPress.org #4. Push out a first version to WordPress.org

The WordPress.org plugins directory is the easiest way for potential users to download and install your plugin. WordPress’ integration with the plugin directory means your plugin can be updated by the user in a couple of clicks.

When you’re ready to release your first version, you’ll want to sign up. After a review process is completed successfully, you’ll be granted a Subversion Repository for your code. The WordPress.org site has good documentation for making your first Subversion commits and the overall process.

Top ↑

5. Embrace open source #5. Embrace open source

Open source is one of the most powerful ideas of our time because it empowers collaboration across borders. By encouraging contributions, you’re allowing others to love your code as much as you do. There are several options to open source your code:

  • Github makes it simple to get others involved with your project. Other developers and users can submit bug fixes or reports, feature requests, or brand new contributions easily. Github has a great documentation portal and even an interactive demo if you’ve never used Git before.
  • Bitbucket is an alternative to Github with similar features.
  • The WordPress.org Plugin Directory provides and requires you to use a Subversion repository.

Top ↑

6. Listen to your users #6. Listen to your users

You’ll often find that your users put your code through many more test cases than you could’ve imagined. This can be tremendously valuable feedback.

Releasing your code through WordPress.org means your plugin automatically has a support forum. Use it! You can subscribe to receive new posts by email and respond to your users in a timely manner. They just want to love your plugin as much as you do.

Andrew Spittle, a Happiness Engineer at Automattic, has a couple good posts on providing support: “Avoiding Easy” and “The Speed of Support.” Jetpack also has a post you can point to about writing great bug reports.

Top ↑

7. Regularly push new versions #7. Regularly push new versions

The best plugins are the ones that keep iterating over time, pushing small changes along the way. Don’t let your hard work go stale by waiting too long to update. Keep in mind, constant upgrades can cause ‘Update Fatigue’ and users will stop upgrading. Keeping a balance between too few updates and too many updates is important.

Top ↑

8. Rinse and repeat #8. Rinse and repeat

Like in other parts of life, the best things come from patience and hard work.

Planning Your Plugin

You’ve written the next Hello Dolly and you want the world to use it. What should you do?

1. Test once and test again #1. Test once and test again

With any luck, your plugin will be used by lots of people in many different situations and hosting environments. You’ll want to make sure you’ve tested your plugin to make sure it works in any situation and doesn’t frustrate your users.

Top ↑

2. Pick a good name #2. Pick a good name

A plugin name should reflect the uniqueness of you and your work. When you pick a name, make sure you’re not violating trademarks or stomping on someone else’s product names. If you’re not working for Facebook, you shouldn’t name your plugin ‘Facebook’s Dancing Squirrels’ after all. A much better name would be ‘Dancing Squirrels for Facebook’ for example. It can be hard to come up with a good name, so take your time. Your plugin URL cannot be changed after you submit it, but the display name can change a thousand times.

Top ↑

3. Write great documentation #3. Write great documentation

A README.txt file is the best place to start, as it’s a standard reference point for all plugins. You’ll want to make sure you include:

  • A concise description of what your plugin actually does. If it does a lot, it might be better as two plugins.
  • Installation instructions, especially if there’s special configuration to be done. If a user needs to register with your service, make sure you link to it.
  • Directions on how to get support, and what you do and do not support.

Top ↑

4. Push out a first version to WordPress.org #4. Push out a first version to WordPress.org

The WordPress.org plugins directory is the easiest way for potential users to download and install your plugin. WordPress’ integration with the plugin directory means your plugin can be updated by the user in a couple of clicks.

When you’re ready to release your first version, you’ll want to sign up. After a review process is completed successfully, you’ll be granted a Subversion Repository for your code. The WordPress.org site has good documentation for making your first Subversion commits and the overall process.

Top ↑

5. Embrace open source #5. Embrace open source

Open source is one of the most powerful ideas of our time because it empowers collaboration across borders. By encouraging contributions, you’re allowing others to love your code as much as you do. There are several options to open source your code:

  • Github makes it simple to get others involved with your project. Other developers and users can submit bug fixes or reports, feature requests, or brand new contributions easily. Github has a great documentation portal and even an interactive demo if you’ve never used Git before.
  • Bitbucket is an alternative to Github with similar features.
  • The WordPress.org Plugin Directory provides and requires you to use a Subversion repository.

Top ↑

6. Listen to your users #6. Listen to your users

You’ll often find that your users put your code through many more test cases than you could’ve imagined. This can be tremendously valuable feedback.

Releasing your code through WordPress.org means your plugin automatically has a support forum. Use it! You can subscribe to receive new posts by email and respond to your users in a timely manner. They just want to love your plugin as much as you do.

Andrew Spittle, a Happiness Engineer at Automattic, has a couple good posts on providing support: “Avoiding Easy” and “The Speed of Support.” Jetpack also has a post you can point to about writing great bug reports.

Top ↑

7. Regularly push new versions #7. Regularly push new versions

The best plugins are the ones that keep iterating over time, pushing small changes along the way. Don’t let your hard work go stale by waiting too long to update. Keep in mind, constant upgrades can cause ‘Update Fatigue’ and users will stop upgrading. Keeping a balance between too few updates and too many updates is important.

Top ↑

8. Rinse and repeat #8. Rinse and repeat

Like in other parts of life, the best things come from patience and hard work.

How to Use Subversion

We’ll describe here some of the basics about using subversion: starting out, changing things, and tagging releases.

This document is not a complete and robust explanation for using SVN, but more a quick primer to get started with plugins on WordPress.org. For more comprehensive documentation, see The SVN Book.

For additional information, please see these documents:

Terminology #Terminology

If you’re new to subversion, you’ll need to know what a few words mean, so let’s go over how subversion behaves to introduce some terms.

All your files will be centrally stored in the svn repository on our servers. From that repository, anyone can check out a copy of your plugin files onto their local machine, but, as a plugin author, only you have tho authority to check in. That means you can make changes to the files, add new files, and delete files on your local machine and upload those changes back to the central server. It’s this process of checking in that updates both the files in the repository and also the information displayed in the WordPress.org Plugin Directory.

Subversion keeps track of all these changes so that you can go back and look at old versions or revisions later if you ever need to. In addition to remembering each individual revision, you can tell subversion to tag certain revisions of the repository for easy reference. Tags are great for labelling different releases of your plugin.

Top ↑

SVN Folders #SVN Folders

The SVN repositories come with four folders:

1
2
3
4
/assets/
/branches/
/tags/
/trunk/

Even if you do your development work elsewhere (like a git repository), we recommend you keep the trunk folder up to date with your code for easy SVN compares.

Top ↑

How To … #How To …

The following sections will walk you through some of the basics of SVN

Start your brand new plugin

All you need to do is add the plugin files you already have to your new SVN repository.

To do that you’ll need to first create a local directory on your machine to house a copy of the SVN repository:

1
$ mkdir my-local-dir

Next, check out the pre-built repository

1
2
3
4
5
$ svn co https://plugins.svn.wordpress.org/your-plugin-name my-local-dir
> A  my-local-dir/trunk
> A  my-local-dir/branches
> A  my-local-dir/tags
> Checked out revision 11325.

As you can see, subversion has added ( “A” for “add” ) all of the directories from the central SVN repository to your local copy. Now you can add your files to the trunk/ directory of your local copy of the repository using copy/paste commands via command line, or dragging and dropping. Whatever you’re comfortable with.

Once your files are in the trunk folder, you must let subversion know you want to add those new files back into the central repository.

1
2
3
my-local-dir/$ svn add trunk/*
> A  trunk/my-plugin.php
> A  trunk/readme.txt

Warning:Do not put your main plugin file in a subfolder of trunk, like /trunk/my-plugin/my-plugin.php as that will break downloads. You may use subfolders for included files.

After you add all your files, you’ll check in the changes back to the central repository.

1
2
3
4
5
my-local-dir/$ svn ci -m 'Adding first version of my plugin'
> Adding trunk/my-plugin.php
> Adding trunk/readme.txt
> Transmitting file data .
> Committed revision 11326.

It’s required to include a commit message for all checkins.

If the commit fails because of ‘Access forbidden’ and you know you have commit access, add your username and password to the check-in command.

1
my-local-dir/$ svn ci -m 'Adding first version of my plugin' --username your_username --password your_password

Edit a file that is already in the repository

We’ll assume you’ve already got your plugin repository filled with the needed files. Now suppose you need to edit one of the files to improve the plugin.

First go into your your local copy of the repository and make sure it’s up to date

1
2
3
$ cd my-local-dir/
my-local-dir/$ svn up
> At revision 11326.

In the above example, we’re all up to date. If there had been changes in the central repository, they would have been downloaded and merged into your local copy.

Now you can edit the file that needs changing using whatever editor you prefer.

If you’re not using an SVN GUI tool (like SubVersion or Coda) you can still check and see what’s different between your local copy and the central repository after you make changes. First we check the status of the local copy:

1
2
my-local-dir/$ svn stat
> M  trunk/my-plugin.php

This tells us that our local trunk/my-plugin.php is different from the copy we downloaded from the central repository ( “M” for “modified” ).

Let’s see what exactly has changed in that file, so we can check it over and make sure things look right.

1
2
3
4
my-local-dir/$ svn diff
> * What comes out is essentially the result of a
  * standard `diff -u` between your local copy and the
  * original copy you downloaded.

If it all looks good then it’s time to check in those changes to the central repository.

1
2
3
4
my-local-dir/$ svn ci -m "fancy new feature: now you can foo *and* bar at the same time"
> Sending    trunk/my-plugin.php
> Transmitting file data .
> Committed revision 11327.

All done!

If the commit fails because of ‘Access forbidden’ and you know you have commit access, add your username and password to the check-in command.

1
my-local-dir/$ svn ci -m 'Adding first version of my plugin' --username your_username --password your_password

“Tag” a new version

Each time you make a formal release of your plugin, you should tag a copy of that release’s code. This lets your users easily grab the latest (or an older) version, it lets you keep track of changes more easily, and lets the WordPress.org Plugin Directory know what version of your plugin it should tell people to download.

To do that you’ll need to remember to update the Stable Tag field in trunk/readme.txt beforehand!

First copy your code to a subdirectory in the tags/ directory. For the sake of the WordPress.org plugin browser, the new subdirectory should always look like a version number. 2.0.1.3 is good. Cool hotness tag is bad.

We want to use svn cp instead of the regular cp in order to take advantage of SVN’s features.

1
2
my-local-dir/$ svn cp trunk tags/2.0
> A tags/2.0

Now, as always, check in the changes.

1
2
3
4
5
my-local-dir/$ svn ci -m "tagging version 2.0"
> Adding         tags/2.0
> Adding         tags/2.0/my-plugin.php
> Adding         tags/2.0/readme.txt
> Committed revision 11328.

Alternately, you can use http URLs to copy, and save yourself bandwidth:

1
my-local-dir/$ svn cp https://plugins.svn.wordpress.org/your-plugin-name/trunk https://plugins.svn.wordpress.org/your-plugin-name/tags/2.0

Doing that will perform the copy remotely instead of copying everything locally and uploading. This can be beneficial if your plugin is larger.

Congratulations! You’ve updated your code!

Top ↑

See Also #See Also

Developer Tools

There are a wide variety of tools available to help with plugin development. Some of them are run in your development environment (xdebug, PHPCS, etc), but there are also some excellent tools that can run right inside WordPress to help you build things properly and diagnose problems.  This chapter deals with the in-browser tools.

Debug Bar and Add-Ons

Debug Bar #Debug Bar

The debug bar, when active, adds a debug menu to the admin bar that shows query, cache, and other helpful debugging information.

When WP_DEBUG is enabled it also tracks PHP Warnings and Notices to make them easier to find.

When SAVEQUERIES is enabled the mysql queries are tracked and displayed.

Visit Debug Bar

Top ↑

Debug Bar Console #Debug Bar Console

This plugin provides a large textarea in which you can run arbitrary PHP.  This is excellent for testing the contents of variables etc.

Visit Debug Bar Console

Top ↑

Debug Bar Shortcodes #Debug Bar Shortcodes

Debug Bar Shortcodes adds a new panel to the Debug Bar that displays the registered shortcodes for the current request.

Additionally it will show you:

  • Which function/method is called by the shortcode.
  • Whether the shortcode is used on the current post/page/post type and how (only when on singular).
  • Any additional information available about the shortcode, such as a description, which parameters it takes, whether or not it is self-closing.
  • Find out all pages/posts/etc on which a shortcode is used.

Visit Debug Bar Shortcodes

Top ↑

Debug Bar Constants #Debug Bar Constants

Debug Bar Constants adds three new panels to the Debug Bar that display the defined constants available to you as a developer for the current request:

  • WP Constants
  • WP Class Constants
  • PHP Constants

Visit Debug Bar Constants

Top ↑

Debug Bar Post Types #Debug Bar Post Types

Debug Bar Post Types adds a new panel to the Debug Bar that displays detailed information about the registered post types for your site.

Visit Debug Bar Post Types

Top ↑

Debug Bar Cron #Debug Bar Cron

Debug Bar Cron adds information about WP scheduled events to a new panel in the Debug Bar. This plugin is an extension for Debug Bar and thus is dependent upon Debug Bar being installed for it to work properly.

Once installed, you will have access to the following information:

  • Number of scheduled events
  • If cron is currently running
  • Time of next event
  • Current time
  • List of custom scheduled events
  • List of core scheduled events
  • List of schedules

Visit Debug Bar Cron

Top ↑

Debug Bar Actions and Filters Addon #Debug Bar Actions and Filters Addon

This plugin adds two more tabs in the Debug Bar to display hooks(Actions and Filters) attached to the current request. Actions tab displays the actions hooked to current request. Filters tab displays the filter tags along with the functions attached to it with respective priority.

Visit Debug Bar Actions and Filters Addon

Top ↑

Debug Bar Transients #Debug Bar Transients

Debug Bar Transients adds information about WordPress Transients to a new panel in the Debug Bar. This plugin is an extension for Debug Bar and thus is dependent upon Debug Bar being installed for it to work properly.

Once installed, you will have access to the following information:

  • Number of existing transients
  • List of custom transients
  • List of core transients
  • List of custom site transients
  • List of core site transients
  • An option to delete a transient

Visit Debug Bar Transients

Top ↑

Debug Bar List Script & Style Dependencies #Debug Bar List Script & Style Dependencies

Lists scripts and styles that are loaded, in which order they’re loaded, and what dependencies exist.

Visit Debug Bar List Script & Style Dependencies

Top ↑

Debug Bar Remote Requests #Debug Bar RemoteRequests

This will log and profile remote requests made through the HTTP API.

This plugin will add a “Remote Requests” panel to Debug Bar that will display the:

  • Request method (GET, POST, etc)
  • URL
  • Time per request
  • Total time for all requests
  • Total number of requests

Optionally, you can add ?dbrr_full=1 to your URL to get additional information, including all request parameters and a full dump of the response with headers.

Visit Debug Bar Remote Requests

Helper Plugins

Query Monitor

Query Monitor is a debugging plugin for anyone developing with WordPress. You can view debugging and performance information on database queries, hooks, conditionals, HTTP requests, redirects and more. It has some advanced features not available in other debugging plugins, including automatic AJAX debugging and the ability to narrow down things by plugin or theme.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s