How to Add a Custom Settings Panel to the Settings Page

How to add a custom settings panel to the LA24Core Settings page.


Overview

The `la24_register_setting` filter adds panels to the LA24Core **Settings** page. Each panel has a priority (display order), a display name, and a callback class that renders the panel HTML including its form.

Hook Signature

php
apply_filters( 'la24_register_setting', array $settings );
**Type:** Filter **Parameters:** `$settings` (array) — the current settings panel registry **Returns:** array — the modified registry

Registering a Settings Panel

php
add_filter( 'la24_register_setting', function ( $settings ) {

    $settings['my_crm_settings'] = array(
        'priority'  => 50,
        'name'      => __( 'My CRM Settings', 'my-crm-ext' ),
        'call_back' => '\MyPlugin\Settings\MyCrmSettings',
    );

    return $settings;
} );

Settings Panel Fields

FieldTypeDescription
Key (array key)stringUnique settings panel identifier
`priority`intTab/panel display order (ascending)
`name`stringPanel label shown in the Settings UI
`call_back`stringFully-qualified settings class name

The Settings Class

The class referenced by `call_back` must **implement the `\LoyalAxis\interfaces\Settings` interface** (`includes/interfaces/Settings.php`):
php
namespace LoyalAxis\interfaces;

interface Settings {
    public static function render_view();
    public static function request_process();
}
Both methods are **static** and take no arguments:
MethodCalled fromResponsibility
`request_process()``GeneralSettings::request_processor()` — once per page load, **before** any panel is renderedRead `$_POST`, sanitize, persist the submitted values, optionally echo a success/error notice
`render_view()``GeneralSettings::tab_content_settings()` — after every registered panel's `request_process()` has already runEcho the panel's HTML/form, pre-filled with the *current* option values
Because every `request_process()` call runs before any `render_view()` call, a value saved during the current request is already up to date by the time `render_view()` reads it back — you never need to reconcile `$_POST` against stored options inside the render method.
Here is a minimal class that follows this contract:
php
<?php

namespace MyPlugin\Settings;

use LoyalAxis\interfaces\Settings;

defined( 'ABSPATH' ) || exit;

class MyCrmSettings implements Settings {

    public static function render_view() {
        $api_key = la24_get_option( 'api_key', 'my_crm_options' );
        ?>
        <form method="post">
            <table class="form-table">
                <tr>
                    <th><?php esc_html_e( 'API Key', 'my-crm-ext' ); ?></th>
                    <td>
                        <input type="text" name="my_crm_api_key"
                               value="<?php echo esc_attr( $api_key ); ?>"
                               class="regular-text" />
                    </td>
                </tr>
            </table>
            <button type="submit" name="my_crm_submit" class="button button-primary">
                <?php esc_html_e( 'Save Settings', 'my-crm-ext' ); ?>
            </button>
        </form>
        <?php
    }

    public static function request_process() {
        if ( isset( $_POST['my_crm_submit'] ) ) {
            la24_update_options( array(
                'api_key' => sanitize_text_field( $_POST['my_crm_api_key'] ?? '' ),
            ), 'my_crm_options' );
        }
    }
}
This mirrors the pattern LA24Core itself uses for every bundled module. The reference implementation is `LA24License_Settings.php` (`includes/plugins/license/includes/classes/LA24License_Settings.php`):
php
class LA24License_Settings implements Settings {

    use Notification;

    public static function render_view() {
        echo Form::start_form_tag( "post", "", "" );
        echo Form::start_form( 0, "la24-section-box" );
        echo Form::header( __( "Software License Management", 'la24core' ), 12, "la24-titr" );

        $enable_sos = la24_get_option( 'enable_sos', 'la24license_options' );
        echo Form::checkbox( __( "Emergency Conditions (New Sales Suspension)", 'la24core' ), 6, "enable_sos", "enable_sos", "", $enable_sos, 0, $enable_sos, '' );

        // ...more fields, each read back with la24_get_option()...

        echo Form::submit_btn( __( "Save Settings", 'la24core' ), 12, "licenseplugin_submit", "la24_licenseplugin_submit" );
        echo Form::close_form();
        echo Form::close_form_tag();
    }

    public static function request_process() {
        if ( isset( $_POST['la24_licenseplugin_submit'] ) ) {
            la24_update_options(
                array(
                    'global_keys'                    => /* ... */,
                    'interval_check_expire_license'  => $_POST['interval_check_expire_license'] ?? '',
                    'enable_sos'                      => $_POST['enable_sos'] ?? '',
                ),
                'la24license_options'
            );

            la24_update_options( array( 'active_setting_tab' => 'plugin_license' ) );
            self::alert( __( 'License management settings saved successfully.', 'la24core' ), true, 'success' );
        }
    }
}
Notice the same shape as `MyCrmSettings` above: `implements Settings`, static `render_view()` that reads values with `la24_get_option()`, and static `request_process()` that gates the save behind `isset( $_POST[''] )` and persists everything with a single `la24_update_options()` call.
> **Tip:** `LA24License_Settings::request_process()` also calls `la24_update_options( array( 'active_setting_tab' => 'plugin_license' ) )` after saving, so the Settings page reopens on the same tab the user just submitted. Do the same with your own panel's key if you want that behavior.
> **Note:** LA24Core's own panels don't add a nonce field — access to the Settings page is already gated by a capability check (`la24_generalsettings_access`) before `request_process()` ever runs. If your extension needs CSRF protection beyond that, you're free to add `wp_nonce_field()` / `wp_verify_nonce()` around your own save logic.
`Form::*` (used above) is LA24Core's internal form-rendering utility (`\LoyalAxis\classes\utilities\Form`) and isn't required — plain HTML, as in the `MyCrmSettings` example, works just as well.

Saving and Reading Options

There are two ways to persist the values your settings panel collects.

Option A — Standard WordPress functions

Use `update_option()` / `get_option()` directly. This works anywhere in WordPress and is the right choice if a value also needs to be read by code outside LA24Core (another plugin, a theme, etc.):
php
// Save
update_option( 'my_crm_api_key', sanitize_text_field( $_POST['api_key'] ?? '' ) );

// Read with a default
$api_key = get_option( 'my_crm_api_key', '' );
Every value saved this way occupies its own row in `wp_options`. If your panel has several fields, group them into one array to keep the table tidy:
php
// Save
update_option( 'my_crm_options', array(
    'api_key' => sanitize_text_field( $_POST['api_key'] ?? '' ),
    'enabled' => (bool) ( $_POST['enabled'] ?? false ),
) );

// Read with defaults
$opts = wp_parse_args( get_option( 'my_crm_options', array() ), array(
    'api_key' => '',
    'enabled' => false,
) );

Option B — LA24 option helpers

`la24_get_option()` and `la24_update_options()` do this grouping for you automatically, and are what every bundled LA24Core module — including `LA24License_Settings.php` above — actually uses:
php
// Save
la24_update_options( array(
    'api_key' => sanitize_text_field( $_POST['api_key'] ?? '' ),
    'enabled' => $_POST['enabled'] ?? '',
), 'my_crm_options' );

// Read
$api_key = la24_get_option( 'api_key', 'my_crm_options' );
$enabled = la24_get_option( 'enabled', 'my_crm_options' );
See the [next section](#tutorial-la24_get_option-and-la24_update_options) for the full walkthrough of how these two functions behave.
Either approach is acceptable inside a settings class — pick whichever fits the rest of your extension. Just don't mix both for the *same* key: `update_option()` and `la24_update_options()` don't know about each other's data, since the latter stores everything as one serialized array under a group name.

Tutorial: la24_get_option() and la24_update_options()

Both functions are declared in `includes/helpers/la24-core-functions.php` and are thin wrappers around the internal `\LoyalAxis\classes\utilities\Options` class:
php
function la24_get_option( $key, $option = 'la24_options' ) {
    return Options::get_option( $key, $option );
}

function la24_update_options( array $data, $option = 'la24_options' ) {
    return Options::update_options( $data, $option );
}
A related helper in the same file, `la24_delete_option( $key, $option = 'la24_options' )`, removes a single key from a group.

The "group" concept

Unlike `update_option()`, which stores one value per WordPress option row, the LA24 helpers store **many keys inside a single serialized WordPress option** — the `$option` argument (also referred to as the "group"). `LA24License_Settings.php` keeps every one of its settings (`global_keys`, `interval_check_expire_license`, `enable_sos`, `product_id_support`, `days_support`, …) inside one group, `'la24license_options'`, saved with a single call:
php
la24_update_options(
    array(
        'global_keys'                    => $temp_global_key,
        'interval_check_expire_license'  => $_POST['interval_check_expire_license'] ?? '',
        'enable_sos'                     => $_POST['enable_sos'] ?? '',
        'product_id_support'             => $_POST['product_id_support'],
        'days_support'                   => $_POST['days_support'],
    ),
    'la24license_options'
);
...and reads each one back individually:
php
$enable_sos = la24_get_option( 'enable_sos', 'la24license_options' );
If you omit the group argument, it defaults to `'la24_options'` — the group LA24Core's own core settings (login, style, general, …) live in. **Always pass an explicit, unique group name for your extension** (e.g. `'my_crm_options'`) so your keys can't collide with LA24Core's own options or another extension's.

`la24_get_option( $key, $group = 'la24_options' )`

Reads `$key` out of the group's stored array:
  1. If the group already has a **non-empty** value for `$key`, that value is returned as-is (arrays included).
  2. Otherwise, LA24Core looks up a `default` registered for `$key` (see below) and returns that instead, or `null` if none is registered.

`la24_update_options( array $data, $group = 'la24_options' )`

Merges `$data` into the group's existing stored array and persists it with a single `update_option()` call:
  • Every key present in `$data` overwrites the corresponding key already stored in the group.
  • **An empty value (`''`, `null`, `false`, `0`, `[]`) removes that key from the group instead of storing it** — so, for example, an unchecked checkbox correctly falls back to its default the next time `la24_get_option()` is called, rather than being stored as an empty string.
  • Keys not present in `$data` are left untouched.
So a typical `request_process()` only needs to pass the fields that were actually submitted:
php
public static function request_process() {
    if ( isset( $_POST['my_crm_submit'] ) ) {
        la24_update_options( array(
            'api_key' => sanitize_text_field( $_POST['api_key'] ?? '' ),
            'enabled' => $_POST['enabled'] ?? '',
        ), 'my_crm_options' );
    }
}

Optional: defaults and sanitization rules

You can register per-key metadata for your group — a `default` value and a sanitize `rule` — by hooking the `la24_settings_option` filter, the same way `LA24License_Hook::settings_option()` does for the License module:
php
add_filter( 'la24_settings_option', function ( $settings ) {
    $settings['my_crm_options'] = array(
        'api_key' => array(
            'rule'    => 'text_field', // run through Sanitizer::text_field() on every save
            'default' => '',
        ),
        'enabled' => array(
            'rule' => 'number',
        ),
    );
    return $settings;
} );
Available `rule` values map directly to methods on `\LoyalAxis\classes\utilities\Sanitizer` — `text_field`, `number`, `url`, `email`, `checkbox`, `html`, and a few `array_of_*` variants for repeatable fields.
This step is optional — `la24_get_option()` / `la24_update_options()` work correctly without it as long as you sanitize `$_POST` yourself before calling `la24_update_options()`, exactly as the examples above do. Registering a `rule` mainly saves you from repeating `sanitize_text_field()` at every call site, and a `default` saves you from repeating `?: 'fallback'` at every read site.

Full Settings Panel Example

For a complete multi-field settings panel with grouped options and success feedback, see the `DealsSettings` class in **developer-guide-la24_extension_examples.md**, modeled directly on the real-world `LA24License_Settings.php` shipped with LA24Core.

Manage Cookies

Choose which cookies this website may use. You can change your choice at any time.

Always on

Required for the website to work, for example to keep you signed in, keep your shopping cart and remember this cookie choice. They cannot be switched off.

Analytics, social media connections, live chat and personalized advertising based on your online activity. These are only used if you allow them.