black flat screen computer monitor

When developing a plugin, more often than not there’s a need for some type of configuration. There must be some ways for either administrators or other user roles to access and manipulate a set of parameters of execution. This is preferably done through pages in the admin interface. In this article we will explore how to create an admin page.

Menu or submenu?

From WordPress’s perspective, an admin page should somehow be attached to the admin menu. That’s because a user must have some way of navigating to the page in question.

There are two functions we can use to define an admin page: add_menu_page() and add_submenu_page(). The first one adds a first-level menu item in the admin menu for our page, while the second adds a submenu item. Those functions have a set of common arguments, namely:

  • $page_title is the content of the <title> tag for the page
  • $menu_title is the menu item’s caption
  • $menu_slug is the unique slug for this menu item
  • $capability is the capability a user has to have in order to access this page
  • $position is the order of the menu item among all other items
  • $callback is a callable that actually renders the page

Apart from those, add_menu_page() has an extra argument, $icon_url, which is evidently used to define the icon to be used for the menu item. Its value can be a Dashicon font icon name, a data URI holding a base64-encoded SVG, or 'none' if we want to define the icon with CSS.

Similarly, add_submenu_page() has an extra argument, $parent_slug. This is used to declare the menu that this submenu item should belong to. It can be a slug created by add_menu_page() or the filename of a standard admin page (like options-general.php).

How to render the admin page

As we mentioned above, the $callback argument defines the function that renders our page in both cases by outputting the necessary HTML. In order to demonstrate this, we will create an example admin page which will be accessible by both a menu with a paperclip icon, and a submenu item under the Tools menu:

function tutorial_admin_page_render() { ?>
  <div class="wrap">
		<h1 class="wp-heading-inline"><?php _e('My Admin Page','tutorial'); ?></h1>
		<hr class="wp-header-end">
		<p>This is a demo admin page.</p>
  </div>
<?php }

function tutorial_admin_menu() {
  add_menu_page(
  	__('Tutorial Admin Page','tutorial'),__('Tutorial','tutorial'),
  	'edit_others_posts','tutorial-menu',
  	'tutorial_admin_page_render',
  	'dashicons-paperclip'
  );
  add_submenu_page(
  	'tools.php',
  	__('Tutorial Admin Page','tutorial'),__('Tutorial','tutorial'),
  	'edit_others_posts','tutorial-submenu',
  	'tutorial_admin_page_render'
  );
}

add_action('admin_menu','tutorial_admin_menu');
PHP

The first thing to notice is that we hook our page definitions to the admin_menu action. Any action that fires before this one will be too soon. Moreover, we omitted the $position argument, which means that we let WordPress decide the suitable position of both menu items. Lastly, notice how we use predefined classes in our html, so that our page retains the familiar admin look-and-feel.

Admin page forms

As we said right from the start, the main reason to make an admin page is parametrization. That implies the existence of parameters manipulated by some type of a form. For our next example, we will transform the rendering function to include a form with some fields:

function tutorial_admin_page_render() { 
	$default_options=[
		'foo'=>'Bar Bazington',
		'car'=>'audi',
	];
	$option=get_option('tutorial_options',$default_options);
	$cars=[
		'opel'=>'Opel',
		'audi'=>'Audi',
		'toyota'=>'Toyota',
		'mercedes'=>'Mercedes',
		'mustang'=>'Mustang',
	];
	?>
  <div class="wrap">

		<h1 class="wp-heading-inline"><?php _e('Tutorial Admin Page','tutorial'); ?></h1>
		<hr class="wp-header-end">

		<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">

			<input type="hidden" name="action" value="tutorial_options">

			<table class="form-table">
			
				<tr>
					<th scope="row">
						<label for="foo"><?php _e("Foo's name",'tutorial'); ?></label>
					</th>
					<td>
						<input 
						  name="foo" id="foo" 
						  class="regular-text" 
						  value="<?php echo $option['foo']; ?>"
						>
					</td>
				</tr>
				
				<tr>
					<th scope="row">
						<?php _e('Car brand','tutorial'); ?>
					</th>
					<td>
						<?php foreach($cars as $value=>$label): ?>
							<label>
								<input 
									type="radio" name="car" 
									value="<?php echo $value; ?>" 
									<?php echo ($value==$option['car'])?'checked':''; ?>
								>
								<?php echo $label; ?>
							</label>
							<br>
						<?php endforeach; ?>
					</td>
				</tr>
				
			</table>

			<p class="submit">
				<input 
				  type="submit" name="submit" 
				  class="button button-primary" 
				  value="<?php _e('Save Options','tutorial'); ?>"
				>
			</p>

		</form>

  </div>
<?php }
PHP

The table scheme above is very common in standard admin screens. Other than that, the form illustrated is pretty straightforward: we present the user with the supposed plugin options stored in the database. But how do we handle the form submission?

Note that the form’s action is /wp-admin/admin-post.php. Moreover, there is a special, hidden field named action with a value of tutorial_options. This combination will trigger a admin_post_tutorial_options action when we POST the form, so in order to handle our form’s submission, we will have to hook on that event:

function tutorial_options_post() {
	$option=[
		'foo'=>$_POST['foo'],
		'car'=>$_POST['car'],
	];
	update_option('tutorial_options',$option);
	wp_redirect(wp_get_referer());
}

add_action('admin_post_tutorial_options','tutorial_options_post');
PHP

The above code is also pretty straightforward: we collect our field values from $_POST[], we update our plugin options and finally we redirect to where we came from. Done!