Demystifying WordPress Settings API using OOP

Kofi Mokome, MTech
14 min readDec 9, 2021

What you’ll learn: Overview

WordPress Settings API is a great tool, introduced in WordPress 2.7. It allows admin pages containing settings forms to be managed semi-automatically.
Working with the Settings API can be frustrating for beginners. In this article, we will write a wrapper class, which will enable us easily use the WordPress Settings API

What you need: Prerequisites

In this article, we will need the following

  1. WordPress installed.
  2. Know how to use/set up a custom WordPress Plugin

Project Setup

Assuming you already have a WordPress instance on your PC. If you have not yet set up WordPress, you can download the latest version from WordPress.org

Setting Up The Plugin

If you are new to WordPress plugins, please check the WordPress Developer site to get started.
Plugins are created in the wp-content/plugins folder of your WordPress instance. Let's call the plugin My Plugin. We will create a folder called my-plugin in wp-content/plugins directory.
Next, we create two files my-plugin.php and readme.txt in our my-plugin directory.

  1. The readme.txt file contains information of your plugin like version number, author name, PHP version, etc. WordPress displays the contents on the WordPress plugin store.
    Let's put the following contents into this file.
=== My Plugin ===
Contributors: theagiledevs
Donate link: https://codelabs.agiledevs.com
Requires at least: 5.5
Tested up to: 5.8
Stable tag: 1.0.0
Requires PHP: 5.4
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
== Description ==
This is my plugin description
  1. The my-plugin.php file contains the code of the plugin. WordPress loads this file first when the plugin is activated. Let's add the following information to be displayed on the plugins page
/**
<?php
* @link www.theagiledevs.com
* @since 1.0.0
* @package my_plugin
*
* @wordpress-plugin
* Plugin Name: My Plugin
* Plugin URI: www.theagiledevs.com
* Description: This is demo plugin
* Version: 1.0.0
* Author: The Agile Devs
* Author URI: www.theagiledevs.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: my-plugin
*/
namespace agile_devs;defined( 'ABSPATH' ) or die( 'Giving To Cesar What Belongs To Caesar' );

One more thing to do. we need to activate My Plugin from the WordPress dashboard. Navigating to /wp-admin/plugins.php, we will find My Plugin in the list of plugins. Click on activate.

That set, our folder structure will look like this. We can move to the next section.

Understanding The WordPress Settings API

Before we dive into the code, let us take a moment to look at the Settings API. According to the WordPress Developer website, 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 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.

In Summary, the WordPress Settings API is made up of 3 parts: setting, sections, fields.

  1. setting defines the setting you would like to create. settings can be registered and unregistered using register_setting() and unregister_setting() functions respectively
  2. sections define a group of related fields. sections are added into asetting using the add_settings_section() function
  3. fields are added into a section. A field can be a text input, date input, select input, etc. they are added into a section using the add_settings_field() function.

All these parts can be rendered using settings_fields() , do_settings_sections() and do_settings_fields()

More and detailed information can be found on the WordPress Developer Website

Setting Up The Class

Now that we have an idea of how the WordPress Settings API works, let’s go ahead and write the wrapper for it.

First, we create a new folder in the my-plugin folder called includes. In this folder, we create a PHP file called Setting.php

In the Setting.php file, we create a class called Setting

Overview Of The Setting Class

The Setting class will have 4 variables:

  1. menu_slug will hold the menu slug of the menu item we with to add the setting to
  2. fields an array containing all the fields we will add to a section
  3. sections an array containing all the sections we will add to a setting
  4. section_id, a temporary value to store the section ID we will be adding fields to.

The following methods will be available in this class

  1. A constructor that takes the menu_slug as a parameter and initializes all fields
  2. save(), saves the setting and adds it to the admin_init action
  3. add_settings(), registers a setting, adds all sections, and adds their respective fields.
  4. default_field_callback(), displays the setting field based on the field type
  5. add_field(), adds all fields to be added to a particular section in the fields array
  6. add_section(), adds all sections to be added into a setting in the sections array. It also sets the temporary section_id field.
  7. default_section_callback(), the default callback function for each section
  8. show_form(), displays the final form

So, our Setting class will look like this

<?phpnamespace agile_devs;class Setting {
private $menu_slug;
private $fields;
private $section_id;
private $sections;
public function __construct( $menu_slug ) {
$this->menu_slug = $menu_slug;
$this->fields = array();
$this->sections = array();
}
public function show_form() { } public function save() {
}
public function add_settings() { } public function default_field_callback( $data ) { } public function add_field( $data ) { } public function add_section( $id, $title = '' ) {

}
public function default_section_callback() { }
}

Adding Sections And Fields

Adding Sections

The unique ID and title of the section are needed when creating a section. The ID and title are added to the $sections array. Finally, we set $section_id to the id of the added section
Note: We can only add one section at a time.

public function add_section( $id, $title = '' ) {
array_push( $this->sections, array( $id, $title ) );
$this->section_id = $id;
}

Adding Fields

When creating a field, data like field type, ID, label, class etc. are passed to the add_field() function. The data is compared to $default_data, adding missing information, using array_merge. Finally, we append the section ID to the field and add the field to the $fields array. This will enable us to know to which section particular field belongs when creating the setting

public function add_field( $data ) {
$default_data = array(
'type' => '',
'id' => '',
'label' => '',
'tip' => '',
'min' => '',
'max' => '',
'input_class' => '', // class for input element
'class' => '', // class for parent element
'options' => array( 'Select a value' => '' ),
'default_option' => '',
'autocomplete' => 'on',
'placeholder' => ''
);
$data = array_merge( $default_data, $data );
$data['section_id'] = $this->section_id;
array_push( $this->fields, $data );
}

Adding The Callback Functions

Sections and fields require a callback function. This callback function will have the content to be displayed on the setting or field.

Section Callback

In this article, we will not focus on the section callback. Personally, I do not put anything in the section callback function. We will leave the callback function blank.

public function default_section_callback() {}

Field Callback

This callback function will contain the content, HTML in our case that will be displayed when the field is called. Based on the type of field, we will have different content to display.

public function default_field_callback( $data ) {
switch ( $data['type'] ) {
case 'text':
echo "<p><input type='text' name='{$data['id']}' value='" . get_option( $data['id'] ) . "' class='{$data['input_class']}' placeholder='{$data['placeholder']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'number':
echo "<p><input type='number' name='{$data['id']}' value='" . get_option( $data['id'] ) . "' min='" . $data['min'] . "' max='" . $data['max'] . "' class='{$data['input_class']}' placeholder='{$data['placeholder']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'textarea':
echo "<p><textarea name='{$data['id']}' id='{$data['id']}' cols='80'
rows='8'
placeholder='{$data['placeholder']}' class='{$data['input_class']}' autocomplete='{$data['autocomplete']}'>" . get_option( $data['id'] ) . "</textarea></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'checkbox':
$state = get_option( $data['id'] ) == 'on' ? 'checked' : '';
echo "<p><input type='checkbox' name='{$data['id']}' id='{$data['id']}' " . $state . " class='{$data['input_class']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'select':
$selected_value = get_option( $data['id'] );
echo "<p><select type='text' name='{$data['id']}' id='{$data['id']}' class='{$data['input_class']}'>";
foreach ( $data['options'] as $key => $value ):?>
<option value='<?php echo $value ?>' <?php echo ( $value === $selected_value ) ? 'selected' : '' ?> ><?php echo $key ?></option>
<?php
endforeach;
echo "</select></p>";
echo "<strong>{$data['tip']} </strong>";
break;
default:
echo "<< <span style='color: red;'>Please enter a valid field type</span> >>";
break;
}
}

For each input type, we set the:

  1. Name of input $data['id]
  2. Value, which is saved in WordPress options. get_option( $data['id'] )
  3. Class, which can be used to apply a custom style to the input $data['input_class']}
  4. Placeholder, for text, number and Textarea input fields, {$data['placeholder']}
  5. Helper text or tip displayed below the field. $data['tip']
  6. Min and max values for number input fields $data['min'] and $data['max']
  7. Options for a select field, $data['options']
  8. And other attributes like the label, $data['label'], autocomplete $data['autocomplete'], etc...

Registering The Setting, Sections, and Fields

The last thing to do is to register the setting, sections and add the fields to their respective sections

  1. For each section, we add the section to the setting using the add_settings_section() function which takes as parameter
  2. The ID of the section $section[0]
  3. The title of the section $section[1]
  4. The callback function for the section
  5. The menu slug of the page where the setting will be displayed,
  6. For each field, we register the field under their respective sections using the add_settings_field() function which takes as parameter:
  7. The ID of the field $field['id]
  8. The title of the field $field['label']
  9. The callback function for the field
  10. The menu slug of the page where the field will be displayed
  11. The ID of the section where the field will be added $field['section_id']
  12. Extra arguments used when outputting the field
  13. We also call the register_setting() function which registers a setting and its data. It takes the section id and field id as parameter
public function add_settings() {		foreach ( $this->sections as $section ) {
add_settings_section(
$section[0],
$section[1],
array( $this, 'default_section_callback' ),
$this->menu_slug );
}
foreach ( $this->fields as $field ) {
add_settings_field(
$field['id'],
$field['label'],
array( $this, 'default_field_callback' ),
$this->menu_slug,
$field['section_id'],
$field
);
register_setting( $field['section_id'], $field['id'] );
}
}

Finally, we add the new setting to the admin_init WordPress action

public function save() {
add_action( 'admin_init', array( $this, 'add_settings' ) );
}

The Show Form Method

This method displays the form. In the next section, we will see two ways to display the form.

Note: All forms created with the Settings API must post to options.php before the data can be saved

public function show_form() {
settings_errors(); ?>
<form method="post" action="options.php">
<?php
foreach ( $this->sections as $section ):
settings_fields( $section[0] );
do_settings_sections( $this->menu_slug );
endforeach;
submit_button();
?>
</form>
<?php
}

settings_errors() is used to automatically display success and error messages at the top of the form

submit_button echoes a submit button, with provided text and appropriate class(es).

Final Content of Setting Class

The Setting.php file will look like this

<?phpnamespace agile_devs;class Setting {
private $menu_slug;
private $fields;
private $section_id;
private $sections;
public function __construct( $menu_slug ) {
$this->menu_slug = $menu_slug;
$this->fields = array();
$this->sections = array();
}
public function show_form() {
settings_errors(); ?>
<form method="post" action="options.php">
<?php
foreach ( $this->sections as $section ):
settings_fields( $section[0] );
do_settings_sections( $this->menu_slug );
endforeach;
submit_button();
?>
</form>
<?php
}
public function save() {
add_action( 'admin_init', array( $this, 'add_settings' ) );
}
public function add_settings() { foreach ( $this->sections as $section ) {
add_settings_section(
$section[0],
$section[1],
array( $this, 'default_section_callback' ),
$this->menu_slug );
}
foreach ( $this->fields as $field ) {
add_settings_field(
$field['id'],
$field['label'],
array( $this, 'default_field_callback' ),
$this->menu_slug,
$field['section_id'],
$field
);
register_setting( $field['section_id'], $field['id'] );
}
}
public function default_field_callback( $data ) {
switch ( $data['type'] ) {
case 'text':
echo "<p><input type='text' name='{$data['id']}' value='" . get_option( $data['id'] ) . "' class='{$data['input_class']}' placeholder='{$data['placeholder']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'number':
echo "<p><input type='number' name='{$data['id']}' value='" . get_option( $data['id'] ) . "' min='" . $data['min'] . "' max='" . $data['max'] . "' class='{$data['input_class']}' placeholder='{$data['placeholder']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'textarea':
echo "<p><textarea name='{$data['id']}' id='{$data['id']}' cols='80'
rows='8'
placeholder='{$data['placeholder']}' class='{$data['input_class']}' autocomplete='{$data['autocomplete']}'>" . get_option( $data['id'] ) . "</textarea></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'checkbox':
$state = get_option( $data['id'] ) == 'on' ? 'checked' : '';
echo "<p><input type='checkbox' name='{$data['id']}' id='{$data['id']}' " . $state . " class='{$data['input_class']}'></p>";
echo "<strong>{$data['tip']} </strong>";
break;
case 'select':
$selected_value = get_option( $data['id'] );
echo "<p><select type='text' name='{$data['id']}' id='{$data['id']}' class='{$data['input_class']}'>";
foreach ( $data['options'] as $key => $value ):?>
<option value='<?php echo $value ?>' <?php echo ( $value === $selected_value ) ? 'selected' : '' ?> ><?php echo $key ?></option>
<?php
endforeach;
echo "</select></p>";
echo "<strong>{$data['tip']} </strong>";
break;
default:
echo "<< <span style='color: red;'>Please enter a valid field type</span> >>";
break;
}
}
public function add_field( $data ) {
$default_data = array(
'type' => '',
'id' => '',
'label' => '',
'tip' => '',
'min' => '',
'max' => '',
'input_class' => '', // class for input element
'class' => '', // class for parent element
'options' => array( 'Select a value' => '' ),
'default_option' => '',
'autocomplete' => 'on',
'placeholder' => ''
);
$data = array_merge( $default_data, $data );
$data['section_id'] = $this->section_id;
array_push( $this->fields, $data );
} public function add_section( $id, $title = '' ) {
array_push( $this->sections, array( $id, $title ) );
$this->section_id = $id;
}
public function default_section_callback() { }
}

How To Use The Setting Class

Now that we have the Setting class ready, let's see how to use it.

Include The Settings Class In The Plugin

First, we need to include it in our project. we will modify my-plugin.php and add the following codes

$error = false;function error_notice( $message = '' ) {
if ( trim( $message ) != '' ):
?>
<div class="error notice is-dismissible">
<p><b>My Plugin: </b><?php echo $message ?></p>
</div>
<?php
endif;
}
add_action( 'admin_notices', 'agile_devs\\error_notice', 10, 1 );// loads classes / files$classes = array(
'Setting.php', //
);
foreach ( $classes as $file ) {
if ( ! $filepath = file_exists( plugin_dir_path( __FILE__ ) . "includes/" . $file ) ) {
error_notice( sprintf( __( 'Error locating <b>%s</b> for inclusion', 'kmgt' ), $file ) );
$error = true;
} else {
include_once plugin_dir_path( __FILE__ ) . "includes/" . $file;
}
}

error_notice() displays a nice warning message at the top of the WordPress dashboard in case one of the files in $classes can not be found. It needs to be added to the admin_notices action

The core files we need, are stored in the $classes array.
We loop through all the files in the $classes array and attempt to import it.

Set up The Menu Page

We will use the MenuPage Helper to create the menu page. You can download it from the GitHub Repository.

First, let’s update the $classes variable to import the helper file.
Note: Copy the downloaded file to the includes folder where the settings class is located

$classes = array(
'Setting.php', //
'MenuPage.php',
);

Using the MenuPage Helper class, we can create a menu page with the following code

if ( ! $error ) {
$menu_page = new MenuPage(
array(
'page_title' => 'My Plugin',
'menu_title' => 'My Plugin',
'capability' => 'read',
'menu_slug' => 'my-plugin',
'icon_url' => 'dashicons-filter',
'position' => null,
'function' => 'agile_devs\\callback_function'
) );
$menu_page->run(); function callback_function() {
echo "My Plugin";
}
}

This will create a menu page called My Plugin. Opening the page will display the text My Plugin at the top

Adding The Setting

Let’s create a sample form using the WordPress Setting API wrapper class

$settings = new Setting( $menu_page->get_menu_slug()  );
$settings->add_section( 'my_plugin_settings' );
$settings->add_field(
array(
'type' => 'checkbox',
'id' => 'my_plugin_message_storage_toggle',
'label' => 'Disable file storage: ',
'tip' => "<span class='text-danger' style='color:red;'>Note: This is an experimental feature.</span><br/>Blocked messages are currently stored in a file. <br/>If you are unable to view blocked messages, disable this option. <br/> <b>Note: </b> Auto delete will be activated if it's currently not enabled"
)
);
$settings->add_field(
array(
'type' => 'checkbox',
'id' => 'my_plugin_message_auto_delete_toggle',
'label' => 'Auto delete messages: ',
'tip' => ''
)
);
$settings->add_field(
array(
'type' => 'text',
'id' => 'my_plugin_message_text',
'label' => 'Text field: ',
'tip' => '',
)
);
$settings->add_field(
array(
'type' => 'select',
'id' => 'my_plugin_message_auto_delete_duration',
'label' => 'Number of days: ',
'options' => array(
'1 Month' => '30',
'1 Day' => '1',
'3 Days' => '3',
'1 Week' => '7',
'2 Weeks' => '14',
),
// 'default_option' => ''
)
);
$settings->add_field(
array(
'type' => 'select',
'id' => 'my_plugin_message_auto_delete_amount',
'label' => 'Number of messages to delete: ',
'options' => array(
'10 Messages' => '10',
'20 Messages' => '20',
'40 Messages' => '40',
'80 Messages' => '80',
),
// 'default_option' => ''
)
);
$settings->save();

Displaying The Form

There are two ways to display a form.

  1. Using the show_form() method to automatically display the form.
function callback_function() {
global $settings;
echo "<h2>My Plugin Settings </h2>";
$settings->show_form();
}
  1. Manually displaying the form
function callback_function() {
?>
<h2>My Plugin Settings </h2>
<?php settings_errors(); ?>
<form method="post" action="options.php">
<?php
settings_fields( 'my_plugin_settings' );
do_settings_sections( 'my-plugin' );
submit_button();
?>
</form>
<?php
}

Whichever option you choose, will lead to the same result

Final Content of The Plugin File

<?php
/**
* @link www.theagiledevs.com
* @since 1.0.0
* @package my_plugin
*
* @wordpress-plugin
* Plugin Name: My Plugin
* Plugin URI: www.theagiledevs.com
* Description: This is demo plugin
* Version: 1.0.0
* Author: The Agile Devs
* Author URI: www.theagiledevs.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: my-plugin
*/
namespace agile_devs;
defined( 'ABSPATH' ) or die( 'Giving To Cesar What Belongs To Caesar' );$error = false;function error_notice( $message = '' ) {
if ( trim( $message ) != '' ):
?>
<div class="error notice is-dismissible">
<p><b>My Plugin: </b><?php echo $message ?></p>
</div>
<?php
endif;
}
add_action( 'admin_notices', 'agile_devs\\error_notice', 10, 1 );// loads classes / files$classes = array(
'Setting.php', //
'MenuPage.php',
);
foreach ( $classes as $file ) {
if ( ! $filepath = file_exists( plugin_dir_path( __FILE__ ) . "includes/" . $file ) ) {
error_notice( sprintf( __( 'Error locating <b>%s</b> for inclusion', 'kmgt' ), $file ) );
$error = true;
} else {
include_once plugin_dir_path( __FILE__ ) . "includes/" . $file;
}
}
if ( ! $error ) {
$menu_page = new MenuPage(
array(
'page_title' => 'My Plugin',
'menu_title' => 'My Plugin',
'capability' => 'read',
'menu_slug' => 'my-plugin',
'icon_url' => 'dashicons-filter',
'position' => null,
'function' => 'agile_devs\\callback_function'
) );
$menu_page->run(); $settings = new Setting( $menu_page->get_menu_slug() );
$settings->add_section( 'my_plugin_settings' );
$settings->add_field(
array(
'type' => 'checkbox',
'id' => 'my_plugin_message_storage_toggle',
'label' => 'Disable file storage: ',
'tip' => "<span class='text-danger' style='color:red;'>Note: This is an experimental feature.</span><br/>Blocked messages are currently stored in a file. <br/>If you are unable to view blocked messages, disable this option. <br/> <b>Note: </b> Auto delete will be activated if it's currently not enabled"
)
);
$settings->add_field(
array(
'type' => 'checkbox',
'id' => 'my_plugin_message_auto_delete_toggle',
'label' => 'Auto delete messages: ',
'tip' => ''
)
);
$settings->add_field(
array(
'type' => 'text',
'id' => 'my_plugin_message_text',
'label' => 'Text field: ',
'tip' => '',
)
);
$settings->add_field(
array(
'type' => 'select',
'id' => 'my_plugin_message_auto_delete_duration',
'label' => 'Number of days: ',
'options' => array(
'1 Month' => '30',
'1 Day' => '1',
'3 Days' => '3',
'1 Week' => '7',
'2 Weeks' => '14',
),
// 'default_option' => ''
)
);
$settings->add_field(
array(
'type' => 'select',
'id' => 'my_plugin_message_auto_delete_amount',
'label' => 'Number of messages to delete: ',
'options' => array(
'10 Messages' => '10',
'20 Messages' => '20',
'40 Messages' => '40',
'80 Messages' => '80',
),
// 'default_option' => ''
)
);
$settings->save();
function callback_function() {
global $settings;
echo "<h2>My Plugin Settings </h2>";
$settings->show_form();
}
}

Takeaways

In this article, we have seen how to make development easier by using object-oriented programming to simplify the WordPress Settings API.

Check out the GitHub repository for more documentation and updates to this helper class.

--

--