Skip to main content
WordPress made easy with the drag & drop Total WordPress Theme!Learn More

How to Make WordPress Themes and Plugins More Usable

Last updated on:
WordPress admin pointer

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.

  1. Admin pointers
  2. Admin notices
  3. Contextual help
  4. Admin bar links
  5. Plugin action and meta links

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_pointers meta 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">
	jQuery(document).ready( function($) {
			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'

} ?>

Which will result in something like this:

WordPress admin pointer

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:

WordPress admin notices

 * 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>
	// 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>
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

WordPress 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',

	// 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 )

		// 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="" 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
				<li><a href="">' . __( 'Our website', 'ts-fab' ) . '</a></li>
				<li><a href="!/thematosoup">Twitter</a></li>
				<li><a href="">Facebook</a></li>
				<li><a href="">Google+</a></li>
				<li><a href="">LinkedIn</a></li>



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).

WordPress 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(
			'settings' => '<a href="' . admin_url( 'tools.php?page=our-settings-page.php' ) . '">' . __( 'Settings', 'ts-fab' ) . '</a>'


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(
			array( '<a href="">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.


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?

Subscribe to the Newsletter

Get our latest news, tutorials, guides, tips & deals delivered to your inbox.


  1. AJ Clarke | WPExplorer

    Thanks so much Slobodan for joining our little community here at WPExplorer 😉 You are a passionate WordPress developer and your skills are superb!

    I’ll be the first to admit that I can make my plugins/themes a bit more user-friendly and will definitely put your tips to use.

    Thanks for sharing! We look forward to more awesome posts!

    • Slobodan Manic


      I could hardly feel more welcome than I do right now 🙂

      It’s a great community to be a part of!

  2. Remi

    This is a great post! That was missing, thanks so much for putting in this post these useful techniques in one single place!

    • Slobodan Manic

      Thanks Remi! I hope many developers will find it useful.

  3. Ranko

    Congrats bro 🙂

    • Slobodan Manic

      Thanks, Ranko 🙂

  4. Gregg

    Another outstanding tutorial. Thank you!

    • Slobodan Manic

      Thanks Gregg, glad to hear you found it useful!

  5. FxB

    Great article. Thanks. I just regreat that everything is not i18n ready. Nowadays it should be No optionnal. For the Rest i learned a lot of nice tips. A must read and use

    • Slobodan Manic


      I agree and I should’ve mentioned that.

      Just for purpose of this tutorial, it wouldn’t make a difference if “Our Settings Page” was translation ready, but it could do damage if someone just copies the code.

      Thanks for your comment.

      • FxB

        It’s exactly for that, to give the best copy/paste possible to future dev especialy when the post is going to be used as a référence cause it deserved it

        • Slobodan Manic

          Yes, great point then, I’ll make sure code is edited, so there are no untranslatable strings. Thanks for spotting that.

  6. sanjay

    Wow, this is awesome Slobodan! I was thinking of using this on one of my themes for clients, hope you share more tips about WP.

    • Slobodan Manic


      Thanks for commenting.

      That’s the plan 🙂

  7. ramzesimus

    Great post! It helps me to increase usability of my themes (especially WP Pointers)
    Thanks )

    • Slobodan Manic

      Glad to hear that! Thanks for commenting.

  8. Rilwis

    I think the contextual help is useful, but not much user friendly. Indeed, I see people rarely use it to find helpful information.

  9. Terry O'Brien

    A couple of questions about Admin Pointers: first off, in the pointer, is it default to have the pointer box point to the Settings list, or how does WordPress know where to point the pointer? How can it be pointed to a plugin-added menu / sub-menu list?

    As for the Meta Links, I’ve copied over the code directly, and all that happens is that every meta link for all plugins disappears. Any idea what is going on? I already used the code for the action links but I’d like to include additional meta links.

    Last: you’ve got a couple of “>= 0” statements in your Contextual Help code.

    • Slobodan Manic

      For admin pointers, if you check Javascript code, there’s this in it:


      So that’s why it’s next to Settings menu, but it can be anywhere on the page.

      Plugin meta links – the code should work, if placed in main plugin file.

      Thanks for catching >=0 errors!

      • Terry O'Brien

        Well, I searched how a couple of other plugins handled the situation, and found they did something different for the ‘add_filter’ call for the ‘plugin_meta_link’: the second parameter was an array where the first entry was the name (string) of the class under which the function was defined and the second entry was the name of the function. Still don’t understand how it works and how it was so different than other calls where I used ‘&$this’ for the first entry.

  10. Terry O'Brien

    Oops. Make that “&gt=0” statements.

  11. ccov

    Thank you for the post, most of all about the plugin action and meta links. Really usable!

  12. Joe privoll

    Great post, Slobodan! very usable. May I use this for my commercial theme?

    • AJ Clarke | WPExplorer

      You should 😉 That’s the whole point, to help make themes out there better!

  13. Joe

    Hey guys quick question,
    (I couldn’t find the answer anywhere so I’m asking you directly), I downloaded one of your awesome themes, but I’m not sure how to install it or use it for my blog. Is this only for premium packages or can I use these customized themes on a free wordpress blog?


    • AJ Clarke | WPExplorer

      Hey Joe,

      The themes on our site are all for Self-Hosted WordPress powered sites ( if you are on you won’t be able to use them.

  14. Dan Bernardic

    Thanks for the tips.

    Just thought I’d let you know that the “Plugin Action and Meta Links” example uses admin_url( ‘/wp-admin/’ … ) which doubles up on the wp-admin.

    • Slobodan Manic

      Nice catch, I’m updating the snippet.

  15. kirkward

    I’d also like to thank you. This is the first tutorial on contextual help that I’ve been able to follow and understand, and that gave me a working solution to build on.

    • Slobodan Manic

      I’m really glad to hear that, thanks!

  16. Websiteguy's Plugins

    How do you make the notice reappear after an update or download of the plugin. The code that you have only displays it one time.


Leave a Reply

Your email address will not be published. Required fields are marked *

Learn how your comment data is processed by viewing our privacy policy here.