LESSON 4 โฑ๏ธ 18 min read

Admin Interface: Menu Pages & Settings API

Adding Admin Menu Pages

WordPress provides several functions to add menu items:

FunctionCreates
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.