Create a WordPress plugin from scratch with settings page and options

Writing a WordPress plugin from scratch is the cleanest way to add custom functionality to a site because it keeps your code separate from the theme, survives theme changes, and can be activated and deactivated without touching core files. Every plugin starts with a single PHP file containing a specific comment header that tells WordPress the plugin name, version, author, and description. The header is the only strict requirement — everything else is regular PHP. The Settings API is the WordPress-approved pattern for adding plugin options: you register a settings group with register_setting(), define individual fields with add_settings_field(), organise them into sections with add_settings_section(), and render the form with the built-in settings_fields() and do_settings_sections() helper functions. WordPress handles nonce generation and verification, option storage in wp_options, and the save redirect automatically when you use the Settings API correctly. The options page itself is registered with add_options_page() inside a admin_menu hook. Each registered option should have a sanitise callback passed as the third argument to register_setting() — this callback is called by WordPress before the value is saved to the database, making it the right place to validate and sanitise user input. Options are retrieved anywhere with get_option() and a safe default value as the second argument. A well-structured plugin also uses a PHP namespace or a unique prefix on all function and class names to avoid conflicts with other plugins. Review the dashboard widgets guide and the sanitisation guide to complement the plugin with a custom admin panel and secure input handling.

Problem: You want to add persistent, configurable functionality to WordPress via a plugin with its own settings page, without modifying the theme or using a third-party plugin framework.

Solution: Create the plugin header file, register a settings page, and use the Settings API to store and retrieve options:

<?php
/**
 * Plugin Name:  Hello Admin Tools
 * Plugin URI:   https://helloadmin.com
 * Description:  Example plugin with a Settings API options page.
 * Version:      1.0.0
 * Author:       Hello Admin
 * License:      GPL-2.0-or-later
 * Text Domain:  ha-tools
 */

if ( ! defined( 'ABSPATH' ) ) exit; // Prevent direct file access

// Register settings, sections, and fields
add_action( 'admin_init', 'ha_tools_register_settings' );

function ha_tools_register_settings() {
    register_setting(
        'ha_tools_group',        // Option group (must match settings_fields())
        'ha_tools_options',      // Option name in wp_options
        'ha_tools_sanitise'      // Sanitise callback
    );

    add_settings_section(
        'ha_tools_general',
        'General Settings',
        '__return_false',        // No section description needed
        'ha-tools'               // Page slug
    );

    add_settings_field(
        'ha_footer_text',
        'Footer text',
        'ha_tools_footer_text_field',
        'ha-tools',
        'ha_tools_general'
    );
}

function ha_tools_footer_text_field() {
    $options = get_option( 'ha_tools_options', [] );
    $value   = isset( $options['footer_text'] ) ? $options['footer_text'] : '';
    echo '<input type="text" name="ha_tools_options[footer_text]"
           value="' . esc_attr( $value ) . '" class="regular-text" />';
}

function ha_tools_sanitise( $input ) {
    $clean = [];
    if ( isset( $input['footer_text'] ) ) {
        $clean['footer_text'] = sanitize_text_field( $input['footer_text'] );
    }
    return $clean;
}

// Add the options page to the Settings menu
add_action( 'admin_menu', 'ha_tools_add_options_page' );

function ha_tools_add_options_page() {
    add_options_page(
        'Hello Admin Tools',
        'HA Tools',
        'manage_options',
        'ha-tools',
        'ha_tools_render_page'
    );
}

function ha_tools_render_page() {
    if ( ! current_user_can( 'manage_options' ) ) return;
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'ha_tools_group' );
            do_settings_sections( 'ha-tools' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

// Use the saved option anywhere
add_filter( 'the_content', 'ha_tools_append_footer_text' );

function ha_tools_append_footer_text( $content ) {
    if ( ! is_singular( 'post' ) ) return $content;
    $options = get_option( 'ha_tools_options', [] );
    if ( ! empty( $options['footer_text'] ) ) {
        $content .= '<p class="ha-footer-note">' . esc_html( $options['footer_text'] ) . '</p>';
    }
    return $content;
}

NOTE: The plugin file must be placed inside its own subdirectory in wp-content/plugins/, for example wp-content/plugins/ha-tools/ha-tools.php — single-file plugins in the plugins root work but are not recommended. Always check current_user_can( ‘manage_options’ ) before rendering or processing any admin page. The register_setting() sanitise callback is called by WordPress on save, so you do not need a separate nonce check — the Settings API handles that internally via settings_fields().