Create Custom Options Pages

WordPress custom settings pages are quite useful when you are developing a plugin or a theme. You will definitely need some place in admin area where users can configure your plugin and Settings API is perfect for that.

And yes, we are going to use Settings API which will help us creating our options pages. It was added in WordPress 2.7 only. I really doubt that you are using a WordPress version prior 2.7, but anyway I think I have to mention that.

Below is the screenshot of a settings page we are going to create in this tutorial. I decided to make it simple, so we have multiple fields – a text field and a checkbox.

how to create a WordPress option page with field validation

Guys, it is super-detailed and step by step guide about Settings API, if you do not have time for this, I highly recommend to proceed to this part of the tutorial.

1. Add an Option Page

It is our very first step – we are going to create an administration menu element with an empty page yet. The good news here is that you can add it anywhere – just after “Dashboard” menu item as a child element of “Tools” menu item.

Add a top-level administration menu page

The only function that can help us here is add_menu_page(). I am going to mention different WordPress functions in this tutorial from time to time – you can find any of them in official WordPress Codex.

So, let’s add our page just after “Dashboard” menu, right?

add_action( 'admin_menu', 'rudr_top_lvl_menu' );
 
function rudr_top_lvl_menu(){
 
	add_menu_page(
		'Slider Settings', // page <title>Title</title>
		'Slider', // link text
		'manage_options', // user capabilities
		'rudr_slider', // page slug
		'rudr_slider_page_callback', // this function prints the page content
		'dashicons-images-alt2', // icon (from Dashicons for example)
		4 // menu position
	);
}
 
function rudr_slider_page_callback(){
	echo 'What is up?';
}

Once you inserted the above code either to your current theme functions.php file or to a custom plugin you should get a result like this:

WordPress top level administration menu
A menu position value equal to 4 allows us to add a menu link just before “Posts” but after “Dashboard” and the separator.

Add administration submenu page

When creating a custom plugin or a theme, I wouldn’t recommend you to use top-level menus, because some users tend to activate up to one hundred plugins on their WordPress websites, which makes the administration menu overloaded and messy.

So, let’s create the same page but place it for example inside “Tools” menu.

Creare admin submenu WordPress

And here is the ready to use code:

add_action( 'admin_menu', 'rudr_submenu' );

function rudr_submenu(){

	add_submenu_page(
		'tools.php', // parent page slug
		'Slider Settings',
		'Slider',
		'manage_options',
		'rudr_slider',
		'rudr_slider_page_callback',
		0 // menu position
	);
}

function rudr_slider_page_callback(){
	echo 'What is up?';
}

The only thing I would highlight here is a parent page slug on line 6. It is a pretty easy thing to get, just go to any top-level menu page and you will see it in URL. And there is more than that – WordPress has alternative functions for every top-level menu element. Let me explain it in a table:

Parent page slugAlternative function
index.php (Dashboard)add_dashboard_page()
edit.php (Posts)add_posts_page()
upload.php (Media)add_media_page()
edit.php?post_type=page (Pages)add_pages_page()
edit-comments.php (Comments)add_comments_page()
themes.php (Appearance)add_theme_page()
plugins.php (Plugins)add_plugins_page()
users.php (Users)add_users_page()
tools.php (Tools)add_management_page()
options-general.php (Settings)add_options_page()

That’s it, now we can easily replace add_submenu_page() function in the above code with add_management_page() function.

add_management_page(
	'Slider Settings',
	'Slider',
	'manage_options',
	'rudr_slider',
	'rudr_slider_page_callback',
	0 // menu position
);

Using custom SVG icons

In the above example, when we created a top-level menu, we added an icon there. We provided its name dashicons-images-alt2 as an add_menu_page() function argument.

I think you already know that WordPress has a vector icon font Dashicons and we can use any icon from it. All we need to do is to provide its name like dashicons-saved or dashicons-palmtree etc.

But what to do if you need to use your specific super-cool custom icon? Still possible. Let me explain how.

  1. Decide what SVG icon you are going to use. For example I chose one of the icons from free FontAwesome set, you can find them on GitHub.
  2. Copy the icon code, begins with <svg.. and paste it somewhere for your convenience.
  3. Change the icon width and height to 20, example: <svg width="20" height="20"...
  4. We need our icon to change its color on hover, to make sure it will happen please check that every <path> element of the icon has fill="black" attribute, example: <path fill="black"...
  5. Add the icon code to add_menu_page() function like this:
$icon = '<svg width="20" height="20" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="black" d="M1280 704q0-26-19-45t-45-19q-172 0-318 49.5t-259.5 134-235.5 219.5q-19 21-19 45 0 26 19 45t45 19q24 0 45-19 27-24 74-71t67-66q137-124 268.5-176t313.5-52q26 0 45-19t19-45zm512-198q0 95-20 193-46 224-184.5 383t-357.5 268q-214 108-438 108-148 0-286-47-15-5-88-42t-96-37q-16 0-39.5 32t-45 70-52.5 70-60 32q-43 0-63.5-17.5t-45.5-59.5q-2-4-6-11t-5.5-10-3-9.5-1.5-13.5q0-35 31-73.5t68-65.5 68-56 31-48q0-4-14-38t-16-44q-9-51-9-104 0-115 43.5-220t119-184.5 170.5-139 204-95.5q55-18 145-25.5t179.5-9 178.5-6 163.5-24 113.5-56.5l29.5-29.5 29.5-28 27-20 36.5-16 43.5-4.5q39 0 70.5 46t47.5 112 24 124 8 96z"/></svg>';

add_menu_page(
	'Slider Settings',
	'Slider',
	'manage_options',
	'rudr_slider',
	'rudr_slider_page_callback',
	'data:image/svg+xml;base64,' . base64_encode( $icon ),
	4
);

And… it works!

Custom SVG icon of a WordPress settings page

2. WordPress Settings API

Adding custom fields with Settings API is a little different that adding them to meta boxes or to taxonomy settings. Maybe you would say it is simpler, at least you do not need nonce checks anymore, maybe you’d say it is more complicated – I don’t know.

Let’s start fill the rudr_slider_page_callback() function with the content!

<?php
function rudr_slider_page_callback(){
	?>
		<div class="wrap">
			<h1><?php echo get_admin_page_title() ?></h1>
			<form method="post" action="options.php">
				<?php
					settings_fields( 'rudr_slider_settings' ); // settings group name
					do_settings_sections( 'rudr_slider' ); // just a page slug
					submit_button(); // "Save Changes" button
				?>
			</form>
		</div>
	<?php
}

Here we have several functions:

  • get_admin_page_title() – just returns the page title, which you set before as a first argument of add_menu_page() function.
  • submit_button() – prints “Save Changes” button.
  • settings_fields() – prints hidden fields of this settings page.
  • do_settings_sections() – prints actually the fields. Pass your settings page slug as this function the only parameter (we set it before in add_menu_page() or add_submenu_page() functions).

Register a setting and create a field

Well, if you’ve never worked with WordPress Settings API, you may find this part quite overwhelming. In order to make it simpler, let’s break down the whole process into 3 easy steps.

  1. We need to create a section for fields. If you don’t want subheadings or don’t want to separate fields into groups, it is OK, but we need a section anyway. Function add_settings_section() would help us with that.
  2. Now it is time to register the field. The registration is quite simple – just run register_setting() function and pass settings group name, option name and a sanitization function name there.
  3. Display the field using add_settings_field() function and a custom callback function to print the field HTML.

Straight to the code:

<?php
add_action( 'admin_init',  'rudr_settings_fields' );
function rudr_settings_fields(){

	// I created variables to make the things clearer
	$page_slug = 'rudr_slider';
	$option_group = 'rudr_slider_settings';

	// 1. create section
	add_settings_section(
		'rudr_section_id', // section ID
		'', // title (optional)
		'', // callback function to display the section (optional)
		$page_slug
	);

	// 2. register fields
	register_setting( $option_group, 'slider_on', 'rudr_sanitize_checkbox' );
	register_setting( $option_group, 'num_of_slides', 'absint' );

	// 3. add fields
	add_settings_field(
		'slider_on',
		'Display slider',
		'rudr_checkbox', // function to print the field
		$page_slug,
		'rudr_section_id' // section ID
	);

	add_settings_field(
		'num_of_slides',
		'Number of slides',
		'rudr_number',
		$page_slug,
		'rudr_section_id',
		array(
			'label_for' => 'num_of_slides',
			'class' => 'hello', // for <tr> element
			'name' => 'num_of_slides' // pass any custom parameters
		)
	);

}

// custom callback function to print field HTML
function rudr_number( $args ){
	printf(
		'<input type="number" id="%s" name="%s" value="%d" />',
		$args[ 'name' ],
		$args[ 'name' ],
		get_option( $args[ 'name' ], 2 ) // 2 is the default number of slides
	);
}
// custom callback function to print checkbox field HTML
function rudr_checkbox( $args ) {
	$value = get_option( 'slider_on' );
	?>
		<label>
			<input type="checkbox" name="slider_on" <?php checked( $value, 'yes' ) ?> /> Yes
		</label>
	<?php
}

// custom sanitization function for a checkbox field
function rudr_sanitize_checkbox( $value ) {
	return 'on' == $value ? 'yes' : 'no';
}

First of all – a couple words about sanitization. As a third argument of register_setting() function you have to pass a sanitizing function name. It could be a WordPress default function (for example I use absint() for number of slides) or a custom one (I created rudr_sanitize_checkbox() function for our checkbox field).

Second – please also keep in mind, that add_settings_field() has a very handy sixth parameter which allows to pass any arguments in it. This parameter will be fully available in a field callback function.

Finally, we have the fields on our settings page:

WordPress add fields to a custom options page

Show success message

The long story short here is the code we would need.

<?php
add_action( 'admin_notices', 'rudr_notice' );

function rudr_notice() {

	if(
		isset( $_GET[ 'page' ] ) 
		&& 'rudr_slider' == $_GET[ 'page' ]
		&& isset( $_GET[ 'settings-updated' ] ) 
		&& true == $_GET[ 'settings-updated' ]
	) {
		?>
			<div class="notice notice-success is-dismissible">
				<p>
					<strong>Slider settings saved.</strong>
				</p>
			</div>
		<?php
	}

}

And now we have a working options page:

Create a custom options page in WordPress

Add fields to WordPress settings pages

In this part of the tutorial I will show you a way how you can skip creating a custom page and add all the settings you need to WordPress standard option pages like “General”, “Writing” etc.

Wouldn’t it be quite logical for example to add our slider settings fields to the “Media” page?

In order to do so we need to provide the default settings page slug in add_settings_section(), register_setting() and add_settings_field(). You know what? I am going to replace just 7 lines of code from here.

// I created variables to make the things clearer
$page_slug = 'media';
$option_group = 'media';

// 1. create section
add_settings_section(
	'rudr_section_id',
	'Slider settings',

And that’s enough for our fields to be displayed on the “Media” page.

add fields to default WordPress option pages

The other page slugs are: general, writing, reading, discussion, permalink.

3. Validate Fields

Above I didn’t touch any validation moments for a simplicity of this tutorial but I definitely definitely going to talk about it in details.

Let’s assume that we have to validate “Number of slides” field and make the minimum value of this field to be equal to 2. I know I know that <input type="number" /> has min attribute but HTML validation is never enough anyway.

In order to make the validation work we are going to use two WordPress functions:

  • settings_errors() – to print errors,
  • add_settings_error() – to register an error.

These two functions should be connected by their first parameter, like settings_errors( 'hey-error' ) and add_settings_error( 'hey-error', ... ). Let’s do it!

Let’s begin with this part of the code and add settings_errors() function there, exactly in the place where the errors are supposed to be displayed.

<?php
function rudr_slider_page_callback(){
	?>
		<div class="wrap">
			<h1><?php echo get_admin_page_title() ?></h1>
			<form method="post" action="options.php">
				<?php
					settings_errors( 'rudr_slider_settings_errors' ); // here we are
					settings_fields( 'rudr_slider_settings' ); 
					...

Now our goal is to customize the field sanitization function from this part of the tut, so will not only sanitize but also validate the field value. So, I am going to replace absint() function with any custom one.

register_setting( $option_group, 'num_of_slides', 'rudr_validate' );

And finally our sanitization/validation function:

function rudr_validate( $input ) {
	// sanitize in the first place
	$input = absint( $input );
 
	if( $input < 2 ) { // some conditions
		add_settings_error(
			'rudr_slider_settings_errors',
			'not-enough', // part of error message ID id="setting-error-not-enough"
			'The minimum amount of slides should be at least 2!',
			'error' // success, warning, info
		);
		// get the previous field value if validation fails
		$input = get_option( 'num_of_slides' );
	}
 
	return $input;
}

And finally, let’s modify our success message:

add_action( 'admin_notices', 'rudr_notice' );

function rudr_notice() {

	$settings_errors = get_settings_errors( 'rudr_slider_settings_errors' );
	// if we have any errors, exit
	if ( ! empty( $settings_errors ) ) {
		return;
	}
	
	...

Just a perfection, isn’t it?

how to create a WordPress option page with field validation

4. Easier example

We have some code above. Actually a lot of code. And it is all about to create a couple of input fields. I want to be honest with you – I never do all that manually on my projects. Because it is so time consuming. What is the solutions then? I also don’t like plugins, especially ACF-like ones. Because I’d better spend a couple more hours and create a fast website instead of using slow overloaded with functionality plugins.

So I created another plugin for myself – Simple Fields. It has the bare minimum of the functionality, that’s why it is lightning fast, just like you code everything in functions.php, but at the same time saves you hours of writing code.

Just compare this code to everything we did above:

add_filter( 'simple_register_option_pages', 'rudr_option_page' );
function rudr_option_page( $option_pages ) {

	$option_pages[] = array(
		'id'	=> 'rudr_slider',
		'title' => 'Slider settings',
		'menu_name' => 'Slider',
		'fields' => array(
			array(
				'id' => 'slider_on',
				'label' => 'Display Slider',
				'type' => 'checkbox',
				'short_description' => 'Yes'
			),
			array(
				'id' => 'num_of_slides',
				'label' => 'Number of slides',
				'type' => 'number',
				'default' => 2,
				'show_if' => array( // plugin also supports conditional logic
					'id' => 'slider_on',
					'value' => 'yes'
				)
			),
 		),
	);
	return $option_pages;
	
}

And here we are:

WordPress settings page with conditional logic
Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X