Skip to main content

Addon Settings API

Most addons need configuration. StoreEngine gives you AbstractAddonSettings — a base class that stores your settings as a single option, exposes a typed reader, and auto-wires your defaults and field definitions into the admin Settings UI without you touching any admin code.

This page is a deep dive on that class. For the addon itself, see Create your first addon.

Anatomy of a settings class

Extend StoreEngine\Classes\AbstractAddonSettings, declare a $settings_name, and implement two methods: get_default_settings() and get_settings_fields(). Here is the real SEO addon's settings class (addons/seo/settings.php), trimmed:

<?php
namespace StoreEngine\Addons\Seo;

use StoreEngine\Classes\AbstractAddonSettings;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

class Settings extends AbstractAddonSettings {

protected ?string $settings_name = 'seo';

public function get_default_settings(): array {
return [
'mode' => 'auto',
'schema_product_enabled' => true,
'schema_breadcrumb_enabled' => true,
'og_enabled' => true,
'twitter_card_type' => 'summary_large_image',
'twitter_site' => '',
'default_share_image_id' => 0,
'org_name' => '',
'org_logo_id' => 0,
'noindex_cart' => true,
'noindex_checkout' => true,
'noindex_account' => true,
'noindex_thankyou' => true,
];
}

public function get_settings_fields(): array {
return [
'mode' => 'string',
'schema_product_enabled' => 'boolean',
'schema_breadcrumb_enabled' => 'boolean',
'og_enabled' => 'boolean',
'twitter_card_type' => 'string',
'twitter_site' => 'string',
'default_share_image_id' => 'number',
'org_name' => 'string',
'org_logo_id' => 'number',
'noindex_cart' => 'boolean',
'noindex_checkout' => 'boolean',
'noindex_account' => 'boolean',
'noindex_thankyou' => 'boolean',
];
}
}

The class uses the AbstractSingleton trait through the base, so you always work with Settings::init().

$settings_name

protected ?string $settings_name is the storage key for the whole option array, following the StoreEngine convention (SEO stores everything under seo). If you leave it null, the base class derives it from the class slug — but declaring it explicitly is clearer and stable across refactors.

get_default_settings(): array

Returns the complete default map, key => value. Every field your addon reads should have a default here. On load_settings() these defaults are merged under the stored values, so a newly added field is never missing even for stores that saved settings before you added it.

get_settings_fields(): array

Returns a key => type map that tells the admin Settings UI how to treat each field. The types used in core are:

TypeMeaning
stringText value.
booleanOn/off toggle.
numberNumeric value (including attachment IDs like org_logo_id).

The keys must match those in get_default_settings(). This map is what StoreEngine merges into the settings-fields payload the admin app consumes.

One key holds the whole addon

All of an addon's settings live in a single option keyed by $settings_name. You read individual values by key through the reader below — you don't register each field as its own WordPress option.

Reading settings

From the addon's own settings class

$mode = Settings::init()->get_settings( 'mode' ); // default from get_default_settings()
$site = Settings::init()->get_settings( 'twitter_site', '' ); // explicit fallback

get_settings( string $key, $default = null ) calls load_settings() (lazy, cached per request), returns the value or your $default, and passes it through the filter storeengine/{settings_name}/get_settings so other code can override reads.

From anywhere in your codebase

The same works wherever you can reference the class:

use StoreEngine\Addons\Seo\Settings;

$og_enabled = Settings::init()->get_settings( 'og_enabled' );

load_settings( bool $reload = false ) populates the cache from get_default_settings() merged with the stored option; pass true to force a re-read after a programmatic save. Settings are also loaded on init (via a hook the base class registers) so translations are available.

Persisting defaults

save_default_settings(): void writes your defaults to storage only if nothing is stored yet:

public function save_default_settings(): void {
if ( false === Helper::get_settings( $this->get_settings_name(), false ) ) {
Base::save_settings( [ $this->get_settings_name() => $this->get_default_settings() ] );
}
}

Call it from your addon's addon_activation_hook() so a freshly enabled addon has sane defaults:

public function addon_activation_hook() {
Settings::init()->save_default_settings();
}

There is also update_settings_name( string $old_name ), which migrates a stored option from an old key to the current $settings_name and deletes the old one — useful if you rename an addon.

Auto-wired admin UI

You don't write any admin code to surface these settings. The base constructor calls dispatch_hooks(), which registers:

HookEffect
initload_settingsLoads settings early (translation-safe).
storeengine/admin/settings_default_data (filter)Merges get_default_settings() under your $settings_name.
storeengine/ajax/settings_fields (filter)Merges get_settings_fields() under your $settings_name.

Because these hooks run in the constructor, simply calling Settings::init() from your addon's init_addon() is enough to register everything:

public function init_addon() {
Settings::init();
// ...rest of your boot code...
}

Save validation

By default settings save without extra validation. To validate on save, set the protected flag and override validate_settings():

class Settings extends AbstractAddonSettings {

protected ?string $settings_name = 'my-feature';
protected bool $validate_on_save = true;

// get_default_settings(), get_settings_fields()...

public function validate_settings( \WP_Error $errors, array $payload ): \WP_Error {
if ( empty( $payload['api_key'] ) ) {
$errors->add( 'my-feature-api_key', __( 'API key is required.', 'my-textdomain' ) );
}
return $errors;
}
}

When $validate_on_save is true, the base class hooks storeengine/ajax/validate_settings and calls your validate_settings() with the accumulated WP_Error and the incoming payload. Important behavior baked into the base:

  • Your key is only validated when it's part of the save. Saves from other settings tabs (General, Compliance, etc.) legitimately omit your key; those are skipped so you don't reject unrelated saves. Stored values are preserved regardless.
  • If your key is present but empty, the base adds a {settings_name}-settings_data error before delegating, guarding against a corrupted payload.

Return the WP_Error (with or without added errors). Any error added blocks the save and surfaces in the admin UI.

Reference

MemberKindPurpose
$settings_name?stringStorage key for the addon's option array.
$validate_on_saveboolWhen true, wires save validation. Default false.
get_default_settings()abstractComplete default key => value map.
get_settings_fields()abstractkey => type map for the admin UI.
get_settings( $key, $default )methodRead a single value (lazy-loads + filters).
load_settings( $reload )methodPopulate/refresh the per-request cache.
save_default_settings()methodPersist defaults if none stored yet.
update_settings_name( $old )methodMigrate a stored option to the current key.
validate_settings( $errors, $payload )methodOverride to validate on save.
get_settings_name()methodReturn the resolved $settings_name.