Demystifying WordPress Settings API using OOP
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
- WordPress installed.
- 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.
- 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
- 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
.
setting
defines the setting you would like to create.settings
can be registered and unregistered using register_setting() and unregister_setting() functions respectivelysections
define a group of related fields.sections
are added into asetting
using the add_settings_section() functionfields
are added into a section. A field can be a text input, date input, select input, etc. they are added into asection
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:
menu_slug
will hold the menu slug of the menu item we with to add the setting tofields
an array containing all the fields we will add to a sectionsections
an array containing all the sections we will add to a settingsection_id
, a temporary value to store the section ID we will be adding fields to.
The following methods will be available in this class
- A constructor that takes the menu_slug as a parameter and initializes all fields
save()
, saves the setting and adds it to theadmin_init
actionadd_settings()
, registers a setting, adds all sections, and adds their respective fields.default_field_callback()
, displays the setting field based on the field typeadd_field()
, adds all fields to be added to a particular section in the fields arrayadd_section()
, adds all sections to be added into a setting in the sections array. It also sets the temporary section_id field.default_section_callback()
, the default callback function for each sectionshow_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:
- Name of input
$data['id
] - Value, which is saved in WordPress options.
get_option( $data['id'] )
- Class, which can be used to apply a custom style to the input
$data['input_class']}
- Placeholder, for text, number and Textarea input fields,
{$data['placeholder']}
- Helper text or tip displayed below the field.
$data['tip']
- Min and max values for number input fields
$data['min']
and$data['max']
- Options for a select field,
$data['options']
- 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
- For each section, we add the section to the setting using the
add_settings_section()
function which takes as parameter - The ID of the section
$section[0]
- The title of the section
$section[1]
- The callback function for the section
- The menu slug of the page where the setting will be displayed,
- For each field, we register the field under their respective sections using the
add_settings_field()
function which takes as parameter: - The ID of the field
$field['id]
- The title of the field
$field['label']
- The callback function for the field
- The menu slug of the page where the field will be displayed
- The ID of the section where the field will be added
$field['section_id']
- Extra arguments used when outputting the field
- 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.
- Using the
show_form()
method to automatically display the form.
function callback_function() {
global $settings;
echo "<h2>My Plugin Settings </h2>";
$settings->show_form();
}
- 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.