Building a great WordPress theme or plugin is as much about making it easy to use as it is about functionality and optimized code. Every time users activate a theme or plugin and struggle to find their way to and around its settings, somewhere in the world a kitten suffers. It would be nice if we put an end to that, right?
Luckily, all you need to do to make your plugins and themes more usable is take advantage of WordPress’ built-in functionality. “Reinventing the wheel” is not on the list of skills required for this. Let’s take a look at some techniques that will help users find their way around and take frustration out of using your plugins and themes.
Admin Pointers
Introduced in WordPress 3.3, WordPress admin pointers are the most aggressive way of grabbing users’ attention, so don’t go pointer crazy. Still, if there’s something you absolutely must tell folks who just installed/upgraded your theme or plugin, WordPress admin pointers are the way to go.
They are very easy to use but poorly documented at WordPress Codex website. Here’s a quick breakdown of how pointers work, followed by an example that adds a pointer next to Settings menu.
- A theme or plugin can register new pointers, assigning a unique ID to each one
- Pointers are shown to users until they click “Dismiss” link
- When that happens, pointer ID is added to user’s
dismissed_wp_pointersmeta key and pointer is no longer shown
And the example, as promised:
/**
* Adds a simple WordPress pointer to Settings menu
*/
function thsp_enqueue_pointer_script_style( $hook_suffix ) {
// Assume pointer shouldn't be shown
$enqueue_pointer_script_style = false;
// Get array list of dismissed pointers for current user and convert it to array
$dismissed_pointers = explode( ',', get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
// Check if our pointer is not among dismissed ones
if( !in_array( 'thsp_settings_pointer', $dismissed_pointers ) ) {
$enqueue_pointer_script_style = true;
// Add footer scripts using callback function
add_action( 'admin_print_footer_scripts', 'thsp_pointer_print_scripts' );
}
// Enqueue pointer CSS and JS files, if needed
if( $enqueue_pointer_script_style ) {
wp_enqueue_style( 'wp-pointer' );
wp_enqueue_script( 'wp-pointer' );
}
}
add_action( 'admin_enqueue_scripts', 'thsp_enqueue_pointer_script_style' );
function thsp_pointer_print_scripts() {
$pointer_content = "<h3>Stop looking for it, it is right here!</h3>";
$pointer_content .= "<p>If you ever activated a plugin, then had no idea where its settings page is, raise your hand.</p>"; ?>
<script type="text/javascript">
//<![CDATA[
jQuery(document).ready( function($) {
$('#menu-settings').pointer({
content: '<?php echo $pointer_content; ?>',
position: {
edge: 'left', // arrow direction
align: 'center' // vertical alignment
},
pointerWidth: 350,
close: function() {
$.post( ajaxurl, {
pointer: 'thsp_settings_pointer', // pointer ID
action: 'dismiss-wp-pointer'
});
}
}).pointer('open');
});
//]]>
</script>
<?php
} ?>
Which will result in something like this:

This was just a simple one, if you want to learn more about WordPress admin pointers check out this article on Integrating With WordPress’ UI: Admin Pointers.
Admin Notices
If admin pointers were a guy holding a big arrow sign in front of a shop, admin notices would be that same guy handing out flyers at a remote location. Not exactly dragging you in, but still grabbing attention. Of course, you don’t want to show notices all the time, so you should either make them dismissible or place them inside a conditional function. Here’s an example:
/**
* Add admin notices
*/
function thsp_admin_notices() {
global $current_user;
$userid = $current_user->ID;
global $pagenow;
// This notice will only be shown in General Settings page
if ( $pagenow == 'options-general.php' ) {
echo '<div class="updated">
<p>This is an "updated" notice.</p>
</div>';
}
// Only show this notice if user hasn't already dismissed it
// Take a good look at "Dismiss" link href attribute
if ( !get_user_meta( $userid, 'ignore_sample_error_notice' ) ) {
echo '<div class="error">
<p>This is an "error" notice. <a href="?dismiss_me=yes">Dismiss</a>.</p>
</div>';
}
}
add_action( 'admin_notices', 'thsp_admin_notices' );
First notice in this example will only be shown in General Settings page. Second one is only shown to users who haven’t dismissed it. As you can see, it checks for current user’s ignore_sample_error_notice user meta field and is displayed only if that field is empty. So how do we add user meta field when they click “Dismiss”? Easy:
/**
* Add user meta value when Dismiss link is clicked
*/
function thsp_dismiss_admin_notice() {
global $current_user;
$userid = $current_user->ID;
// If "Dismiss" link has been clicked, user meta field is added
if ( isset( $_GET['dismiss_me'] ) && 'yes' == $_GET['dismiss_me'] ) {
add_user_meta( $userid, 'ignore_sample_error_notice', 'yes', true );
}
}
add_action( 'admin_init', 'thsp_dismiss_admin_notice' );
We’re hooking into admin_init action and checking if dismiss_me GET parameter has been set. Since href attribute for our “Dismiss” link is ?dismiss_me=yes once a user clicks it, user meta field is added and it’s good bye notice.
Contextual Help

Imagine a world in which any documentation you need is at your fingertips, exactly and only when you need it. Now let’s make it happen.
Contextual help not only makes this possible, but also very easy. Let’s register a settings page for our plugin so we can add some contextual help to it.
/**
* Add settings page, under Settings menu
*/
function thsp_add_settings_page() {
global $thsp_settings_page;
$thsp_settings_page = add_options_page(
'Our Settings Page',
'Our Settings Page',
'manage_options',
'thsp_settings_page',
'thsp_show_settings_page'
);
// Check if WP version is 3.3 or higher, add contextual help
global $wp_version;
if ( version_compare( $wp_version, '3.3') >= 0 ) {
add_action( 'load-' . $thsp_settings_page, 'thsp_add_help_tabs' );
}
}
add_action( 'admin_menu', 'thsp_add_settings_page' );
We won’t be dealing with settings page call back function – thsp_show_settings_page, since it falls out of scope of this blog post. If you need to learn about WordPress settings pages Tom McFarlin of Wptuts+ has got you covered. Anyway, the bit of code we really want to take a deeper look at is this:
// Check if WP version is 3.3 or higher, add contextual help
global $wp_version;
if ( version_compare( $wp_version, '3.3') >= 0 ) {
add_action( 'load-' . $thsp_settings_page, 'thsp_add_help_tabs' );
}
WordPress 3.3 or higher is required, since we’ll be using add_help_tab function to add contextual help tabs. Notice how hook used in add_action has a variable in it – 'load-' . $thsp_settings_page? This makes sure thsp_add_help_tabs function only gets hooked in settings page we just registered. Brilliant.
Now, here’s the function that adds help tabs:
/**
* Callback function for contextual help, requires WP 3.3
*/
function thsp_add_help_tabs () {
global $wp_version;
if ( version_compare( $wp_version, '3.3') >= 0 ) {
global $thsp_settings_page;
$screen = get_current_screen();
// Check if current screen is settings page we registered
// Don't add help tab if it's not
if ( $screen->id != $thsp_settings_page )
return;
// Add help tabs
$screen->add_help_tab( array(
'id' => 'thsp_first_tab',
'title' => __( 'First tab', 'thsp_contextual_help' ),
'content' => __( '
<p>Yeah, you can even embed videos, nice!</p>
<iframe width="560" height="315" src="http://www.youtube-nocookie.com/embed/RBA-lH2a6E8" frameborder="0" allowfullscreen></iframe>
', 'thsp_contextual_help'
),
) );
$screen->add_help_tab( array(
'id' => 'thsp_second_tab',
'title' => __( 'Second tab', 'thsp_contextual_help' ),
'content' => __( '
<p>I\'m just a second tab that no one will ever click.</p>
', 'thsp_contextual_help'
),
) );
// Set help sidebar
$screen->set_help_sidebar(
'
<ul>
<li><a href="http://thematosoup.com">' . __( 'Our website', 'ts-fab' ) . '</a></li>
<li><a href="http://twitter.com/#!/thematosoup">Twitter</a></li>
<li><a href="http://www.facebook.com/ThematoSoup">Facebook</a></li>
<li><a href="http://plus.google.com/104360438826479763912">Google+</a></li>
<li><a href="http://www.linkedin.com/company/thematosoup">LinkedIn</a></li>
</ul>
'
);
}
}
We just need to check if WordPress version is 3.3 or higher, make sure we’re on the correct page and add help tab using add_help_tab function and help sidebar using set_help_sidebar. Everything else is plain HTML.
If there are any downsides to contextual help it’s that majority of WordPress users aren’t even aware of it (Screen Options, too). So, maybe a pointer, to make sure they don’t miss it?
WordPress Admin Bar Links

These are very handy, especially for logged in users browsing front-end of their sites. They provide one click access to most important dashboard functions and if you feel like your theme or plugin deserves a spot in WordPress Admin Bar, this is how easy it is to do it:
/**
* Admin bar customization
*/
function thsp_admin_bar_links() {
global $wp_admin_bar;
// Adds a new submenu to an existing to level admin bar link
$wp_admin_bar->add_menu( array(
'parent' => 'new-content',
'id' => 'install_plugin',
'title' => __( 'Plugin', 'thsp_admin_bar' ),
'href' => admin_url( 'plugin-install.php' )
) );
// Adds a new top level admin bar link and a submenu to it
$wp_admin_bar->add_menu( array(
'parent' => false,
'id' => 'custom_top_level',
'title' => __( 'Top Level', 'thsp_admin_bar' ),
'href' => '#'
) );
$wp_admin_bar->add_menu( array(
'parent' => 'custom_top_level',
'id' => 'custom_sub_menu',
'title' => __( 'Sub Menu', 'thsp_admin_bar' ),
'href' => '#'
) );
// Removes an existing top level admin bar link
$wp_admin_bar->remove_menu( 'comments' );
}
add_action( 'wp_before_admin_bar_render', 'thsp_admin_bar_links' );
We’re using wp_before_admin_bar_render action hook to modify $wp_admin_bar object before it gets rendered. Example above adds a submenu to an existing top level link (New), a new top level link with another link nested inside it (Top Level, Sub Menu) and removes an existing top level link (Comments).
Plugin Action and Meta Links

Dashboard Plugins screen shows a list of all installed plugins. You can see each plugin’s name, description, version, links to author and plugin website and action links – a combination of Activate, Deactivate, Edit, Delete, depending on whether plugin is activated or not.
For some plugins that’s good enough. But if your plugin has a settings page, I’d like to hear one good reason not to add an action link to it, especially if it’s as simple as this:
/**
* Add action links in Plugins table
*/
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'thsp_plugin_action_links' );
function thsp_plugin_action_links( $links ) {
return array_merge(
array(
'settings' => '<a href="' . admin_url( 'tools.php?page=our-settings-page.php' ) . '">' . __( 'Settings', 'ts-fab' ) . '</a>'
),
$links
);
}
You should add this code to your plugin’s main file (plugin-name.php) so proper hook can be used. For example, if your plugin’s main file really is plugin-name.php, ‘plugin_action_links_plugin-name’ filter hook will be used, making sure action links are added only for your plugin. Another one of those magic WordPress moments.
Plugin row meta links are slightly different. Hook name is not dynamic, so you need to pass two arguments to your custom function, an array of existing links and current plugin links are being processed for:
/**
* Add meta links in Plugins table
*/
add_filter( 'plugin_row_meta', 'thsp_plugin_meta_links', 10, 2 );
function thsp_plugin_meta_links( $links, $file ) {
$plugin = plugin_basename(__FILE__);
// create link
if ( $file == $plugin ) {
return array_merge(
$links,
array( '<a href="http://twitter.com/thematosoup">Follow us on Twitter</a>' )
);
}
return $links;
}
It’s up to you to select which links to show where, so if you never dealt with plugin action and meta links before, check list of your installed plugins to see how other developers are doing it.
Conclusion
Some smart planning, common sense and WordPress built-in functionality can get you a long way, but as with everything else in life, moderation is key. Too many unneeded admin pointers or admin bar links can be frustrating.
You don’t have to be a usability expert to make usable WordPress products.
Got any other techniques you’d like to share?
