WordPress Settings API is a robust API but it’s not a framework to easily create fields within your WordPress admin area. Let’s build a simple framework utilizing the Settings API.

We’ll create an abstract class that will hold all the basic functionality we need for creating Settings Pages.

Several years ago, I’ve written a similar tutorial on How to create WordPress Menu Pages with OOP. This one is not so focused on the whole OOP approach and it’s heavily dependant on the Settings API. The article from before, has it’s own way of saving data and displaying the fields.

Settings API Framework Abstract Class

You can save this file inside of your plugin or theme to try it out. I like to put my abstract classes inside of an abstract folder. You can name the file as you want, I’ll call it class-settings.php.

<?php
namespace MySettingsFramework;
abstract class Settings {
/**
* Setting ID. Prefixes all options.
* @var string
*/
protected $id = '';
/**
* @var string
*/
protected $page_title = '';
/**
* @var string
*/
protected $menu_title = '';
/**
* @var string
*/
protected $parent_menu = '';
/**
* Settings Fields.
* @var array
*/
protected $fields = [];
public function __construct() {
add_action( 'admin_menu', [ $this, 'register_page' ] );
add_action( 'admin_init', [ $this, 'register_fields' ] );
}
}

Let’s go over the code we have here:

  • $id – This is not a DB id. It’s a string ID that will prefix our options and such. It’ll also be used as the menu/submenu slug.
  • $page_title – This is the Page Title that will show on the browser tab when viewing the page
  • $menu_title – This is the text that will display on the Menu in the admin
  • $parent_menu – The menu slug under which we want this menu to be
  • $fields – This will hold the configuration of our fields for that specific Settings Page

Then, inside the constructor method, we use action hooks where we’ll register the menus and also the settings.

Registering the Settings Pages

<?php
namespace MySettingsFramework;
abstract class Settings {
// .. code before
public function register_page() {
if ( $this->parent_menu ) {
add_submenu_page(
$this->parent_menu,
$this->page_title,
$this->menu_title ?: $this->page_title,
'manage_options',
$this->id . '_settings_page',
[ $this, 'render_page']
);
} else {
add_menu_page(
$this->page_title,
$this->menu_title ?: $this->page_title,
'manage_options',
$this->id . '_settings_page',
[ $this, 'render_page']
);
}
}
}

Inside this method, we’re registering the menu or submenu. Our menu & submenu pages will have the slug formed by the $id of the class and the suffix _settings_page: this->id . '_settings_page'.

Displaying the Page

Now, let’s display the page.

<?php
namespace MySettingsFramework;
abstract class Settings {
// code before ...
public function render_page() {
?>
<div class="wrap">
<div id="icon-options-general" class="icon32"><br></div>
<h2><?php echo $this->page_title; ?></h2>
<form action="options.php" method="post">
<?php
do_action( $this->id . '_settings_before_options' );
// Prepare form to handle current page.
settings_fields( $this->id . '_settings_page' );
// Render fields and sections.
do_settings_sections( $this->id . '_settings_page' );
do_action( 'pmpro_' . $this->id . '_settings_before_submit_button' );
submit_button( __( 'Save Settings', 'your_textdomain' ) );
do_action( $this->id . '_settings_after_submit_button' );
?>
</form>
<?php do_action( $this->id . '_settings_after_form' ); ?>
</div>
<?php
}
}

We have a simple HTML page with a form that will send the request to options.php. From there, WordPress will know where to save it and how. That’s with the help of the method settings_fields.

Then, with the method do_settings_sections we display sections and fields that are registered to our settings page.

We also have a few custom actions here in case you want to display data other than fields on the settings page.

Registering Fields with the WordPress Settings Framework

Before we register the fields, let’s create some helper methods.

<?php
namespace MySettingsFramework;
abstract class Settings {
// code before...
public function get_settings_id() {
return $this->id;
}
public function add_field( $field ) {
$this->fields = array_merge( $this->fields, $field );
}
/**
* Get Settings Fields.
*
* @return mixed|null
*/
public function get_fields() {
return apply_filters( $this->id . '_get_fields', $this->fields );
}
}
  • get_settings_id – This method is not too useful in this class since we can easily get the $id, but if you’re passing the object to other classes or functions, we can retrieve the ID if needed,
  • add_field – same as above, not used here. But if you have some classes that might need to add other fields to it, that’s a method that can be used,
  • get_fields – This is the method that we’ll use to get fields. Since it also has a filter applied, other plugins or such could hook into it and add fields (or remove some).
<?php
namespace MySettingsFramework;
abstract class Settings {
// code before ...
/**
* Register Fields
*
* @return void
*/
public function register_fields() {
$fields = $this->get_fields();
register_setting(
$this->id . '_settings_page',
$this->id . '_options',
[
'sanitize_callback' => [ $this, 'sanitize' ]
]
);
foreach ( $fields as $field ) {
if ( 'section' === $field['type'] ) {
add_settings_section(
$this->id . '_' . $field['name'] . '_section',
$field['title'],
[ $this, 'render_section' ],
$this->id . '_settings_page',
$field
);
} else {
add_settings_field(
$this->id . '_' . $field['name'],
$field['title'],
[ $this, 'render_field' ],
$this->id . '_settings_page',
$this->id . '_' . $field['section'] . '_section',
$field
);
}
}
}
}

This is the important part. We are registering the fields that will be used on our settings page.

We’re registering a single option here and all fields are saved under that one option.

Then, all the fields are registered as setting fields. Sections are registered to the page. Fields are registered to the both page and section.

Sanitizing Options in the WordPress Settings Framework

Since we have also registered a sanitizing method, this will run before saving the data inside of our database.

For the sake of this tutorial, we’ll keep it short. We’ll focus only on text fields.

<?php
namespace MySettingsFramework;
abstract class Settings {
// code before ...
public function sanitize( $input ) {
$fields = $this->get_fields();
foreach ( $fields as $field ) {
// No need to sanitize sections.
if ( 'section' === $field['type'] ) {
continue;
}
if ( ! isset( $input[ $field['name'] ] ) ) {
continue;
}
switch ( $field['type'] ) {
case 'text':
$input[ $field['name'] ] = sanitize_text_field( $input[ $field['name'] ] );
break;
}
}
do_action( $this->id . '_settings_sanitized', $input, $fields, $_POST, $this );
return $input;
}
}

We get all the fields and go over them. If it’s a section, we’ll skip it since it’s not something that will get saved.

The $input variable holds the data that is posted, that’s trying to be saved.

Then, we go over the field type and define how we’ll sanitize the data.

We also have an action that will trigger once all data is saved. This can be useful for different type of data and such.

Helper Methods for Rendering Fields

Let’s now build a few helper methods that will help us when rendering fields.

<?php
namespace MySettingsFramework;
abstract class Settings {
// code before ...
/**
* Get option key. Useful for name attributes in forms.
*
* @param string $key Field Name.
* @return string
*/
public function get_option_key( $key ) {
return $this->id . '_options[' . $key . ']';
}
/**
* Get Option
*
* @param string $id Field Name
* @param mixed $default Default value if we don't have anything saved.
* @return mixed|string
*/
public function get_option( $id, $default = '' ) {
$options = get_option( $this->id . '_options' );
return isset( $options[ $id ] ) ? $options[ $id ] : $default;
}
public function render_section( $args ) {
if ( ! empty( $args['description'] ) ) {
?>
<p class="description"><?php echo wp_kses_post( $args['description'] ); ?></p>
<?php
}
}
/**
* Render Field
* @param array $args Field Arguments.
* @return void
*/
public function render_field( $args ) {
$this->{'render_' . $args['type'] }( $args );
}
}
  • get_option_key – This method will help us when building the field for the name attribute. We can easily provide the field ID/name and this method will return the whole value for the name attribute that we need for saving the data,
  • get_option – This method is used for getting the value we need for the field we’re displaying. We get the whole data that was saved before. If our field was saved, it will return the data even if an empty string. If not, it will return the default one we provide,
  • render_section – This method is used for rendering all sections,
  • render_field – This method is used for rendering all fields. We call a method formed from the field type.

Rendering Text Field in the WordPress Settings Framework

We’ll define the method render_text. This method will be called for a field with the type text.

<?php
namespace MySettingsFramework;
abstract class Settings {
// code before ...
/**
* Render Text input
*
* @param $args
* @return void
*/
public function render_text( $args ) {
$default = ! empty( $args['default'] ) ? $args['default'] : '';
?>
<input type="text" class="widefat" name="<?php echo esc_attr( $this->get_option_key( $args['name'] ) ); ?>" value="<?php echo esc_attr( $this->get_option( $args['name'], $default ) ); ?>" />
<?php
if ( ! empty( $args['description'] ) ) {
?>
<p class="description"><?php echo esc_html( $args['description'] ); ?></p>
<?php
}
}
}

Rendering Checkbox Field in the WordPress Settings Framework

This part is available only to the members. If you want to become a member and support my work go to this link and subscribe: Become a Member

Page Example with the WordPress Settings Framework

Let’s now see a simple example of a page that will show 1 section and 1 text field.

<?php
namespace MySettingsFramework;
class Page extends Settings {
public function __construct() {
$this->id = 'my_page';
$this->page_title = 'My Page';
$this->menu_title = 'Page';
$this->define_fields();
parent::__construct();
}
protected function define_fields() {
$this->fields['my_section'] = [
'name' => 'my_section',
'title' => 'My Custom Section',
'description' => 'My Custom Description',
'type' => 'section'
];
$this->fields['my_text'] = [
'name' => 'my_text',
'title' => 'My Text Field',
'description' => 'My Field Description',
'type' => 'text',
'section' => 'my_section'
];
}
}
view raw page.php hosted with ❤ by GitHub

And let’s say that we want to include both the abstract class and this class. How to include them and show the page?

Just by instantiating the Page class, we’ll be able to see the Page and its settings.

namespace MySettingsFramework;
include_once 'path/to/class/class-settings.php';
include_once 'path/to/class/class-page.php';
new Page();

Conclusion

Well, now, with this little class, we can extend it further by adding other different types such as select, radio and such types.

This part is available only to the members. If you want to become a member and support my work go to this link and subscribe: Become a Member

Become a Sponsor

Posted by Igor Benic

Web Developer who mainly uses WordPress for projects. Working on various project through Codeable & Toptal. Author of several ebooks at https://leanpub.com/u/igorbenic.

Leave a reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.