baxtian / wp_settings
Class to be inherite to create settings
Requires
- php: >=8.1
Requires (Dev)
- baxtian/merak: ^0.5.30
- baxtian/merak-env: ^0.7.6
- timber/timber: ^2.0
README
Class to be inherited to create a WP Settings.
Mantainers
Juan SebastiΓ‘n Echeverry baxtian.echeverry@gmail.com
π Usage Guide
This library requires a minimum of two configuration files: one to define the Page and one to define a Section within that page.
1. Defining the Settings Page
The page configuration file sets up the main menu item, its location, and basic properties within the WordPress dashboard.
| Property | Description | Required? | Example Value |
|---|---|---|---|
slug | The unique identifier (slug) for this settings page in WordPress. | Yes | my_plugin_options |
page_title | The title displayed at the top of the settings screen. | Yes | My Plugin Settings |
sections | An array containing instances of the defined Section classes. | Yes | See examples below. |
menu_title | The text displayed for the menu/sub-menu item in the dashboard. | Yes | Plugin Options |
parent_slug | The slug of the existing parent menu this page belongs to. | Yes | options-general.php |
Parent Slugs for Standard WordPress Menus:
- Appearance:
themes.php- Tools:
tools.php- Settings:
options-general.php- (Use a custom slug for a new top-level menu.)
<?php
namespace My_Plugin\Settings;
use My_Plugin\Settings\Section\Style;
use Baxtian\WP_Settings;
/**
* Configuration page
*/
class My_Plugin extends WP_Settings
{
use \Baxtian\SingletonTrait;
protected function __construct()
{
$this->slug = 'my_plugin';
$this->parent_slug = 'options-general.php';
$this->sections = [
new Style()
];
add_action('init', [$this, 'init']);
parent::__construct();
}
public function init()
{
$this->page_title = __('My Plugin Settings', 'my_plugin');
$this->menu_title = __('My Plugin', 'my_plugin');
}
}
Filename: src/Settings/My_Plugin.php
2. Defining a Section
The section configuration defines a tab or logical grouping of settings within the Page.
| Property | Description | Required? | Example Value |
|---|---|---|---|
slug | The unique identifier (slug) for this section within the page. | Yes | general_settings_tab |
title | The title displayed for the section's tab. | Yes | General Settings |
subsections | An array containing the definitions for each subsection. | Yes | See Subsection Properties below. |
3. Defining Subsections and Fields
The subsections array holds definitions for the fields that will actually store data, grouped logically for display.
Subsection Properties
Each item in the subsections array defines a block of related fields:
| Property | Description | Notes |
|---|---|---|
slug | The unique identifier (slug) for this subsection within the section. | Used internally for grouping fields. |
title | The displayed title for this subsection block. | Set to false to suppress the title display. |
description | Descriptive text displayed below the title. | Set to false to suppress the description. |
fields | An array defining all input fields for this subsection. | See Field Properties below. |
Field Properties
Each item in the fields array defines a single input control:
| Property | Description | Default | Available Types |
|---|---|---|---|
name | The unique option name used to retrieve this field's stored value. | ||
label | The descriptive label displayed next to the field. | ||
type | The type of input field to render. | text | text, checkbox, dropdown, number, password |
default | The fallback value used if the option has not yet been saved by the user. | ||
description | Help text displayed beneath the input field. | Set to false to hide. | |
class | Custom CSS classes to apply to the input element. | ||
editable_if | A callable evaluated (with no arguments) on every save. When it returns false, the submitted value for this field is discarded and the previously stored value is restored β enforced server-side in the sanitize_callback, independent of whether the field is rendered readonly/disabled in the browser. | null (always editable) |
<?php
namespace My_Plugin\Settings\Section;
use Baxtian\WP_Settings\Section;
/**
* Section of the configuration
*/
class Style extends Section
{
public function __construct()
{
$this->slug = 'style';
add_action('init', [$this, 'init']);
parent::__construct();
}
public function init()
{
$this->title = __('Style', 'my_plugin');
$this->subsections = [
[
'slug' => 'colors',
'title' => __('Colors', 'my_plugin'),
'description' => false,
'fields' => [
[
'name' => 'text_color',
'label' => __('Text color', 'my_plugin'),
'class' => false,
'description' => false,
'default' => 'black',
'type' => 'string',
],
[
'name' => 'text_background',
'label' => __('Text background color', 'my_plugin'),
'class' => false,
'description' => false,
'default' => 'silver',
'type' => 'string',
],
],
],
];
}
}
Filename: src/Settings/Section/Style.php
4. Extending Sections from Another Plugin
WP_Settings::__construct() applies the filter wp_settings_sections_{slug} (where {slug} is the transformed slug β for the page-owning class from section 1 above, $this->slug = 'my_plugin' yields 'config_my_plugin') right after computing $this->slug and before linking sections to it. This lets a separate plugin add its own Section to an existing settings page without modifying its source:
<?php
namespace Other_Plugin\Settings;
class Sections
{
use \Baxtian\SingletonTrait;
protected function __construct()
{
add_filter('wp_settings_sections_config_my_plugin', [$this, 'add_section']);
}
public function add_section($sections)
{
$sections[] = new Section\MySection();
return $sections;
}
}
Computing the filter name: the slug passed to the filter is str_replace('-', '_', sanitize_title('Config-' . $original_slug)). For $this->slug = 'my_plugin' this yields config_my_plugin.
Load-order guarantee: the wp_settings_sections_{slug} filter is applied on the init action (priority 20) β not in the constructor, and not lazily on first render. This means the page-owning class can be constructed eagerly, at plugin load time, even by a theme that extends it: themes finish loading after every plugin, so a filter added from functions.php would otherwise always be registered too late for a filter applied in the constructor. init runs after every plugin/theme's normal loading has registered its add_filter() call, but β importantly β before admin_init, which is when each Section registers its fields via add_settings_section()/add_settings_field(). A section added by the filter still needs to exist before admin_init fires for its fields to register; resolving any later (e.g. on first render) would miss that window entirely.
Reading values from outside the owning plugin: a Section added via this filter still stores its values in the page-owning class's option (e.g. config_my_plugin, not one scoped to the extending plugin/theme). If you're that extending plugin/theme and need to read a value elsewhere in your own code (not from your Section, which already gets option_name for free β see below), read the option directly with WordPress's native get_option('config_my_plugin', [])['field_name'] ?? $default, not by instantiating the page-owning class (My_Plugin\Settings\My_Plugin::get_instance()->get_option(...), the class from section 1 that declared $this->slug = 'my_plugin'). Two reasons:
- Decoupling. The option name is the real, stable contract between the two β it's what your own
Sectionalready writes to and what the page's installer/activation code already reads/writes. The class that owns the page is an internal implementation detail of that plugin; depending on its name/namespace couples you to something it's free to restructure without warning. - No exception path.
WP_Settings::get_option()throws if the field isn't registered in the resolved sections or has no default β an unnecessary failure mode for code (e.g. a front-end template) that should just fall back to a default instead of fataling.
Code that lives inside the page-owning plugin (alongside the WP_Settings subclass itself) doesn't need this β calling its own class's get_option() there is a normal in-package dependency, not a cross-boundary one.
5. Retrieving an Option Value
To retrieve a field's value, call the instance of the settings class and use the get_option() method. If the field has not been configured by the user, the system automatically returns the defined default value. If the field is not registered or does not have a default value, the system generates an exception.
use My_Plugin\Settings\My_Plugin as Settings;
.
.
.
$settings = Settings::get_instance();
$text_color = $settings->get_option('text_color');
$text_background = $settings->get_option('text_background');
Changelog
0.1.14
- π fix: resolve sections on
init(not lazily on first render/save/get_option()as in 0.1.13) β 0.1.13 broke every section's fields, because eachSection::__construct()hooks its ownadmin_initto register fields, and resolving as late as first render meantadmin_inithad already fired by the time a filter-added section was constructed.initstill runs after every plugin/theme has registered itsadd_filter()call, but early enough for the resulting sections'admin_inithooks to fire normally.
0.1.13
- π fix: defer the
wp_settings_sections_{slug}filter from the constructor to first use, so a settings page constructed eagerly at plugin load time doesn't resolve its sections before a theme (which always loads after every plugin) has had the chance to register its ownadd_filter()call.
0.1.12
- β¨ feat: add
editable_ifto restrict field editing by permission.
0.1.11
- β¨ feat(settings): keep the tab active after saving the settings
0.1.9
- Add filter
wp_settings_sections_{slug}inWP_Settings::__construct(), allowing other plugins to extend an existing settings page with their own sections.
0.1.8
- Add type image.
- Sanitize checkbox.
- Use toggler for checkboxes.
0.1.5
- Add type dropdown.
0.1.4
- Keep opened last selected tab.
0.1.3
- Add type textarea.
0.1.2
- Documentation.
0.1.1
- First stable release.