Admin Interface: Menu Pages & Settings API
Adding Admin Menu Pages
WordPress provides several functions to add menu items:
| Function | Creates |
|---|---|
add_menu_page() | Top-level menu item |
add_submenu_page() | Submenu under existing menu |
add_options_page() | Submenu under Settings |
add_management_page() | Submenu under Tools |
Creating the Main Menu
<?php
// src/Admin/AdminMenu.php
namespace TaskManagerAdmin;
class AdminMenu {
private string $capability = 'manage_options';
public function register(): void {
// Main menu page
add_menu_page(
__( 'Task Manager', 'task-manager' ), // Page title
__( 'Tasks', 'task-manager' ), // Menu title
$this->capability, // Capability
'task-manager', // Menu slug
[ $this, 'render_tasks_page' ], // Callback
'dashicons-yes-alt', // Icon
30 // Position
);
// Submenu: All Tasks (replaces duplicate from parent)
add_submenu_page(
'task-manager',
__( 'All Tasks', 'task-manager' ),
__( 'All Tasks', 'task-manager' ),
$this->capability,
'task-manager', // Same slug as parent
[ $this, 'render_tasks_page' ]
);
// Submenu: Add New
add_submenu_page(
'task-manager',
__( 'Add New Task', 'task-manager' ),
__( 'Add New', 'task-manager' ),
$this->capability,
'task-manager-add',
[ $this, 'render_add_page' ]
);
// Submenu: Settings
add_submenu_page(
'task-manager',
__( 'Task Manager Settings', 'task-manager' ),
__( 'Settings', 'task-manager' ),
$this->capability,
'task-manager-settings',
[ $this, 'render_settings_page' ]
);
}
public function render_tasks_page(): void {
// Check permissions
if ( ! current_user_can( $this->capability ) ) {
wp_die( __( 'Permission denied.', 'task-manager' ) );
}
include TM_PLUGIN_DIR . 'templates/admin/tasks-page.php';
}
public function render_add_page(): void {
if ( ! current_user_can( $this->capability ) ) {
wp_die( __( 'Permission denied.', 'task-manager' ) );
}
include TM_PLUGIN_DIR . 'templates/admin/add-task.php';
}
public function render_settings_page(): void {
if ( ! current_user_can( $this->capability ) ) {
wp_die( __( 'Permission denied.', 'task-manager' ) );
}
include TM_PLUGIN_DIR . 'templates/admin/settings.php';
}
}
Menu Icons: Use Dashicons (e.g.,
dashicons-yes-alt) or a custom SVG: 'data:image/svg+xml;base64,' . base64_encode($svg)
The Settings API
WordPress Settings API handles:
- Form security (nonces)
- Data validation and sanitization
- Option storage
- Error messages
Registering Settings
<?php
// src/Admin/Settings.php
namespace TaskManagerAdmin;
class Settings {
private string $option_group = 'task_manager_settings';
private string $option_name = 'task_manager_options';
public function __construct() {
add_action( 'admin_init', [ $this, 'register_settings' ] );
}
public function register_settings(): void {
// Register the option
register_setting(
$this->option_group,
$this->option_name,
[
'type' => 'array',
'sanitize_callback' => [ $this, 'sanitize_options' ],
'default' => $this->get_defaults(),
]
);
// Add settings section
add_settings_section(
'general_section',
__( 'General Settings', 'task-manager' ),
[ $this, 'render_section_intro' ],
'task-manager-settings'
);
// Add fields
add_settings_field(
'tasks_per_page',
__( 'Tasks Per Page', 'task-manager' ),
[ $this, 'render_number_field' ],
'task-manager-settings',
'general_section',
[
'id' => 'tasks_per_page',
'description' => __( 'Number of tasks to show per page.', 'task-manager' ),
]
);
add_settings_field(
'enable_api',
__( 'Enable REST API', 'task-manager' ),
[ $this, 'render_checkbox_field' ],
'task-manager-settings',
'general_section',
[
'id' => 'enable_api',
'description' => __( 'Allow external access to tasks via REST API.', 'task-manager' ),
]
);
add_settings_field(
'default_status',
__( 'Default Task Status', 'task-manager' ),
[ $this, 'render_select_field' ],
'task-manager-settings',
'general_section',
[
'id' => 'default_status',
'options' => [
'pending' => __( 'Pending', 'task-manager' ),
'in_progress' => __( 'In Progress', 'task-manager' ),
'completed' => __( 'Completed', 'task-manager' ),
],
]
);
}
public function get_defaults(): array {
return [
'tasks_per_page' => 20,
'enable_api' => true,
'default_status' => 'pending',
];
}
public function get_option( string $key ): mixed {
$options = get_option( $this->option_name, $this->get_defaults() );
return $options[ $key ] ?? $this->get_defaults()[ $key ] ?? null;
}
public function sanitize_options( array $input ): array {
$output = [];
$output['tasks_per_page'] = absint( $input['tasks_per_page'] ?? 20 );
$output['tasks_per_page'] = max( 1, min( 100, $output['tasks_per_page'] ) );
$output['enable_api'] = ! empty( $input['enable_api'] );
$valid_statuses = [ 'pending', 'in_progress', 'completed' ];
$output['default_status'] = in_array( $input['default_status'] ?? '', $valid_statuses, true )
? $input['default_status']
: 'pending';
return $output;
}
// Field rendering methods...
public function render_section_intro(): void {
echo '<p>' . esc_html__( 'Configure the Task Manager plugin settings.', 'task-manager' ) . '</p>';
}
public function render_number_field( array $args ): void {
$value = $this->get_option( $args['id'] );
printf(
'<input type="number" id="%1$s" name="%2$s[%1$s]" value="%3$s" class="small-text" min="1" max="100" />',
esc_attr( $args['id'] ),
esc_attr( $this->option_name ),
esc_attr( $value )
);
if ( ! empty( $args['description'] ) ) {
printf( '<p class="description">%s</p>', esc_html( $args['description'] ) );
}
}
public function render_checkbox_field( array $args ): void {
$value = $this->get_option( $args['id'] );
printf(
'<input type="checkbox" id="%1$s" name="%2$s[%1$s]" value="1" %3$s />',
esc_attr( $args['id'] ),
esc_attr( $this->option_name ),
checked( $value, true, false )
);
if ( ! empty( $args['description'] ) ) {
printf( '<label for="%s"> %s</label>', esc_attr( $args['id'] ), esc_html( $args['description'] ) );
}
}
public function render_select_field( array $args ): void {
$value = $this->get_option( $args['id'] );
printf(
'<select id="%1$s" name="%2$s[%1$s]">',
esc_attr( $args['id'] ),
esc_attr( $this->option_name )
);
foreach ( $args['options'] as $key => $label ) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr( $key ),
selected( $value, $key, false ),
esc_html( $label )
);
}
echo '</select>';
}
}Settings Page Template
<?php
// templates/admin/settings.php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php settings_errors(); ?>
<form method="post" action="options.php">
<?php
settings_fields( 'task_manager_settings' );
do_settings_sections( 'task-manager-settings' );
submit_button();
?>
</form>
</div>Tabbed Settings Interface
For more complex plugins, use tabs:
<?php
// templates/admin/settings-tabbed.php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
$tabs = [
'general' => __( 'General', 'task-manager' ),
'notifications' => __( 'Notifications', 'task-manager' ),
'advanced' => __( 'Advanced', 'task-manager' ),
];
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<nav class="nav-tab-wrapper">
<?php foreach ( $tabs as $tab_id => $tab_name ) : ?>
<a href="<?php echo esc_url( add_query_arg( 'tab', $tab_id ) ); ?>"
class="nav-tab <?php echo $active_tab === $tab_id ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $tab_name ); ?>
</a>
<?php endforeach; ?>
</nav>
<?php settings_errors(); ?>
<form method="post" action="options.php">
<?php
settings_fields( 'task_manager_settings' );
switch ( $active_tab ) {
case 'notifications':
do_settings_sections( 'task-manager-notifications' );
break;
case 'advanced':
do_settings_sections( 'task-manager-advanced' );
break;
default:
do_settings_sections( 'task-manager-general' );
}
submit_button();
?>
</form>
</div>Admin Notices
Display feedback messages:
<?php
// Add admin notice
add_action( 'admin_notices', function() {
if ( ! get_transient( 'task_manager_notice' ) ) {
return;
}
$notice = get_transient( 'task_manager_notice' );
delete_transient( 'task_manager_notice' );
?>
<div class="notice notice-<?php echo esc_attr( $notice['type'] ); ?> is-dismissible">
<p><?php echo esc_html( $notice['message'] ); ?></p>
</div>
<?php
});
// Set notice (e.g., after form submission)
set_transient( 'task_manager_notice', [
'type' => 'success', // success, error, warning, info
'message' => __( 'Task created successfully!', 'task-manager' ),
], 30 );Custom Admin Styles
/* assets/css/admin.css */
.task-manager-wrap {
max-width: 1200px;
}
.task-manager-wrap .card {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.task-manager-wrap .stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.task-manager-wrap .stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.task-manager-wrap .stat-card h3 {
font-size: 32px;
margin: 0 0 5px;
}
.task-manager-wrap .stat-card p {
margin: 0;
opacity: 0.9;
}Next Steps
In Lesson 5, we'll create a custom database table and implement full CRUD operations using WordPress's $wpdb class.
๐ฏ Lesson Complete! You can now build professional admin interfaces with the Settings API.