Tabs with Settings API in WordPress

WordPress themes and plugins come with quite a lot of features and options these days, and the Settings API does provide a very easy to sectioning method. Sections are good and work out of the box, but if you’ve got more options than your sections can handle, it’s wise enough to split them into different options pages and provide tabs. In this post we’ll cover tabs usage via the Settings API in WordPress.

I’m pretty sure you’ve seen tabs in the WordPress admin panel before and I agree they were somewhat ugly and not very user friendly prior to WordPress 3.0 but with quite a major design and UI overhaul, tabs these days look something like this:

Tabs in WordPress

Getting Started

Before getting started with this tutorial, I assume you’re familiar with the basics of the Settings API in WordPress, comfortable with actions and filters and have created Administration Menus before for your themes or plugins.

In this tutorial we’ll create a WordPress plugin that doesn’t really do anything, but does have an options page with two tabbed sections — General and Advanced settings. The plugin itself is for learning purposes but could easily be adapted to existing WordPress plugins or provide the basis for new ones, i.e. used as a boilerplate.

We’ll take an object-oriented approach this time, so no more my_plugin_ prefixes, we’ll keep the code clean and shiny in a class called Settings_API_Tabs_Demo_Plugin. If this is your first time with such an approach, be extra careful, especially around callback functions which in an object-oriented way are passed on a little bit differently than in the functional way, since they’re actually object methods.

For brevity I’ll post the class declaration part only once and the rest of the snippets would go inside that class, i.e. they’ll become the class methods and I’ll refer to them as methods too throughout the whole post to avoid confusion. I’ve also attached the complete source for the plugin at the end of this article.

The Plugin Class Initialization

Let’s take a look at the following snippet which shows our main plugin class, a few of it’s member variables and it’s initialization at the end. This is our base “skeleton” and the rest of the code would go inside as class methods. Oh and don’t forget the plugin file header which I left out for brevity.

class Settings_API_Tabs_Demo_Plugin {

    private $general_settings_key = 'my_general_settings';
    private $advanced_settings_key = 'my_advanced_settings';
    private $plugin_options_key = 'my_plugin_options';
    private $plugin_settings_tabs = array();

    function __construct() { }

add_action( 'plugins_loaded', create_function( '', '$settings_api_tabs_demo_plugin = new Settings_API_Tabs_Demo_Plugin;' ) );

As you can see, I’ve got a class which is initialized with a new keyword during plugins_loaded at the end of the declaration. The class itself contains a constructor method (we’ll fill it out later) which is fired upon initialization and four member variables.

The first two are the database keys for our general and advanced settings, used with get_option later on. So in the end we’ll have two options arrays, one for our general settings and one for our advanced settings. These will be used to split the options themselves using tabs in our plugin options page. I know this might be a little bit confusing and you’ll end up having five different options arrays if you need five tabs, but this is the cleanest of all ways ;)

The third member variable is used when adding an admin menu page (you’ll see it in action very soon) and the fourth variable is an empty array that will be populated with some data about tabs later on. You shouldn’t worry about these two just yet.

Class Constructor, Loading Settings & Defaults

In the previous snippet the __construct method was empty, so here’s how it should actually look like:

function __construct() {
    add_action( 'init', array( &$this, 'load_settings' ) );
    add_action( 'admin_init', array( &$this, 'register_general_settings' ) );
    add_action( 'admin_init', array( &$this, 'register_advanced_settings' ) );
    add_action( 'admin_menu', array( &$this, 'add_admin_menus' ) );

Remember the callback gotcha I mentioned earlier? Here it is in action. As you can see we’re using an array to pass callback functions to add_action which actually means they’re methods of $this object. It’s quite a convenient way to keep method names generic while not worrying about conflicting with other plugins or the WordPress core in terms of function names, just make sure your class name is unique.

The ampersand before $this means that only a reference to $this is passed but not $this itself. We don’t want to create multiple instances or copies of our class, right? We only need one object which has already been initialized. Objects are passed by reference by default in PHP5 but just to be on the safe side ;) If you’re still confused, you should read about passing by reference.

So, back to our constructor. As you can see it attaches four methods to different actions using add_action. The first one is the load_settings method that runs during init. The second and third handle settings registration with the Settings API during admin_init and the last one creates our administration menu entry.

Loading settings is quite simple and straightforward, I’m sure you’ve done something like this before. Just not that we’ve got two options keys now, not one, so we kinda do things twice. So here’s our load_settings method:

function load_settings() {
    $this->general_settings = (array) get_option( $this->general_settings_key );
    $this->advanced_settings = (array) get_option( $this->advanced_settings_key );

    // Merge with defaults
    $this->general_settings = array_merge( array(
        'general_option' => 'General value'
    ), $this->general_settings );

    $this->advanced_settings = array_merge( array(
        'advanced_option' => 'Advanced value'
    ), $this->advanced_settings );

Easy, right? Populate two member variables (arrays) with their respective options from the database and then merge them with some default options for when they’re not set. Watch out for the arguments order to array_merge since the latter will overwrite the former (unless keys are numeric, not our case). Now that this is sorted let’s move on to the fun part — the Settings API.

Settings Registration

We have used two callback methods during our constructor. They look very much alike but represent different tabs, each having it’s own set of sections and fields. Let’s take a look at the first one:

function register_general_settings() {
    $this->plugin_settings_tabs[$this->general_settings_key] = 'General';

    register_setting( $this->general_settings_key, $this->general_settings_key );
    add_settings_section( 'section_general', 'General Plugin Settings', array( &$this, 'section_general_desc' ), $this->general_settings_key );
    add_settings_field( 'general_option', 'A General Option', array( &$this, 'field_general_option' ), $this->general_settings_key, 'section_general' );

If you’ve worked with the Settings API before, you’re familiar with all the three functions that we used, but what’s that first line? Well, that adds an element to our tabs array called General. As I already mentioned, we’ll loop through that array to render our tabs afterwards.

As for the settings themselves, we registered a setting (using our general key member variable, remember?), a section and a field. Let’s quickly walk through the callback methods that we used.

function section_general_desc() { echo 'General section description goes here.'; }
function field_general_option() {
    <input type="text" name="<?php echo $this->general_settings_key; ?>[general_option]" value="<?php echo esc_attr( $this->general_settings['general_option'] ); ?>" />

The first method simply outputs some description text for the section. The second method prints a text element to store our field value. Note the usage of the general settings key member variable for the name attribute and the general settings array for the value. The advanced settings looks very similar:

function register_advanced_settings() {
    $this->plugin_settings_tabs[$this->advanced_settings_key] = 'Advanced';

    register_setting( $this->advanced_settings_key, $this->advanced_settings_key );
    add_settings_section( 'section_advanced', 'Advanced Plugin Settings', array( &$this, 'section_advanced_desc' ), $this->advanced_settings_key );
    add_settings_field( 'advanced_option', 'An Advanced Option', array( &$this, 'field_advanced_option' ), $this->advanced_settings_key, 'section_advanced' );

function section_advanced_desc() { echo 'Advanced section description goes here.'; }

function field_advanced_option() {
    <input type="text" name="<?php echo $this->advanced_settings_key; ?>[advanced_option]" value="<?php echo esc_attr( $this->advanced_settings['advanced_option'] ); ?>" />

So, add a key for the tab, register setting, section and field, section description and field control. Easy as that, right? So far so good, not too much about the actual tabs but we’ll get there very soon, don’t worry :)

Admin Menu, Options Page and… Tabs!

Since the settings registration has now been sorted, let’s go ahead and create a menu entry in our admin panel. This should be no surprise if you created menu pages before. Here’s the source for our add_admin_menus method.

function add_admin_menus() {
    add_options_page( 'My Plugin Settings', 'My Settings', 'manage_options', $this->plugin_options_key, array( &$this, 'plugin_options_page' ) );

I mentioned the plugin options key before and here you can see it being used. We could have hard-coded our key in but it’s easier to find yourself around in this fashion. The plugin_options_page method is being used as the callback function, so let’s see how that looks:

function plugin_options_page() {
    $tab = isset( $_GET['tab'] ) ? $_GET['tab'] : $this->general_settings_key;
    <div class="wrap">
        <?php $this->plugin_options_tabs(); ?>
        <form method="post" action="options.php">
            <?php wp_nonce_field( 'update-options' ); ?>
            <?php settings_fields( $tab ); ?>
            <?php do_settings_sections( $tab ); ?>
            <?php submit_button(); ?>

Hey, there’s something about tabs here, finally! Our plugin options page will contain tabs that would be links to the same plugin options page, only setting an extra tab GET variable, like this:


So we read that variable to figure out what the currently selected tab is, and then use it when calling settings_fields and do_settings_sections. In our case, $tab is one of the two settings keys that we defined at the very beginning of our class. As you might have mentioned, we didn’t print the title of our settings page like we normally do, instead we called the plugin_options_tabs method which looks something like this:

function plugin_options_tabs() {
    $current_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : $this->general_settings_key;

    echo '<h2 class="nav-tab-wrapper">';
    foreach ( $this->plugin_settings_tabs as $tab_key => $tab_caption ) {
        $active = $current_tab == $tab_key ? 'nav-tab-active' : '';
        echo '<a class="nav-tab ' . $active . '" href="?page=' . $this->plugin_options_key . '&tab=' . $tab_key . '">' . $tab_caption . '</a>';
    echo '</h2>';

Again, we grab the current tab, this time only to set an active/non-active class when rendering the tabs markup. We use the screen_icon WordPress function to, err.. render an icon of course! And a foreach loop through our plugin_settings_tabs member variable, which we populated when we registered settings, remember? In every loop pass we output an anchor element with some special classes inside a nav-tab-wrapper which makes our links look like tabs at the top.

Note how the plugin options key is used here again to specify the links of each tab, and the tab key that specifies which settings (general or advanced) should be rendered in our plugin_options_page method. After saving and activating the plugin, you should see an options page with two tabs — General and Advanced, like this:

Tabs via Settings API in WordPress


So we’re done! Of course the plugin itself doesn’t do anything, but for the purpose of illustrating how to work with tabs and the Settings API it’s fine ;) Feel free to use this plugin to adopt your settings in existing plugins, or provide settings in new ones. For your convenience, I’ve attached the source file too which includes some extra code comments.

Download Source Code

Thank you so much for reading and hope everything’s clear enough and easy to understand. Feel free to ask questions or simply say “hi” using our comment form below. Don’t forget to subscribe to our feed too!