docs/extensions/settings-and-config/settings-ui.md
The settings UI is an opt-in path for rendering WooCommerce settings pages with React while keeping the existing WC_Settings_Page registration and save flow.
It is designed for extension authors who want to migrate incrementally. PHP still owns page registration, settings schema, permissions, script dependencies, and persistence. React owns field rendering and client-side interaction.
settings-ui feature flag.Automattic\WooCommerce\Admin\Settings.A complete integration has the same pieces whether it is a full settings tab or a section inside an existing tab:
WC_Settings_Page tab, or a registered section under an existing tab.component name.form_post save adapter unless the field is display-only or manages persistence separately.For local testing, enable the feature with a small mu-plugin:
<?php
add_filter(
'woocommerce_admin_features',
static function ( array $features ): array {
$features[] = 'settings-ui';
return array_values( array_unique( $features ) );
}
);
A WC_Settings_Page subclass opts in by returning a settings UI adapter from get_settings_ui_page().
For pages that only need native fields, use LegacySettingsPageAdapter:
<?php
use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
class My_Plugin_Settings_Page extends WC_Settings_Page {
public function __construct() {
$this->id = 'my_plugin';
$this->label = __( 'My plugin', 'my-plugin' );
parent::__construct();
}
public function get_settings_ui_page(): ?SettingsUIPageInterface {
return new LegacySettingsPageAdapter( $this );
}
}
WooCommerce only uses the adapter when the settings-ui feature flag is enabled. Returning an adapter does not change the page while the feature flag is disabled.
Extensions can register a complete settings section under an existing WooCommerce settings tab. The section object defines where the section lives, how it is labelled, which fields it renders, which scripts power custom React components, and how fields are saved.
This is useful for payment providers or integrations that should live inside a Core-owned tab such as WooCommerce > Settings > Payments.
<?php
use Automattic\WooCommerce\Admin\Settings\SettingsSection;
use Automattic\WooCommerce\Admin\Settings\SettingsSectionRegistry;
final class My_Plugin_Settings_Section extends SettingsSection {
public function get_parent_page_id(): string {
return 'checkout';
}
public function get_id(): string {
return 'my_plugin';
}
public function get_label(): string {
return __( 'My plugin', 'my-plugin' );
}
public function get_settings( WC_Settings_Page $parent_page ): array {
return array(
array(
'title' => __( 'My plugin', 'my-plugin' ),
'type' => 'title',
'id' => 'my_plugin_options',
),
array(
'title' => __( 'Payment methods', 'my-plugin' ),
'id' => 'my_plugin_payment_methods',
'type' => 'multiselect',
'component' => 'my-plugin/payment-method-picker',
'options' => array(
'card' => __( 'Card', 'my-plugin' ),
'bnpl' => __( 'Buy now, pay later', 'my-plugin' ),
),
),
array(
'type' => 'sectionend',
'id' => 'my_plugin_options',
),
);
}
public function get_script_handles( WC_Settings_Page $parent_page ): array {
return array( 'my-plugin-settings-ui' );
}
// The inherited save adapter is `form_post` by default.
}
add_action(
'woocommerce_settings_sections_registration',
function ( SettingsSectionRegistry $registry ): void {
$registry->register( new My_Plugin_Settings_Section() );
}
);
WooCommerce creates the settings UI adapter for registered sections internally. When the settings UI feature flag is disabled, WooCommerce falls back to the legacy settings returned by get_settings(). Saves continue through the existing WooCommerce settings form flow and section-specific hooks such as woocommerce_update_options_checkout_my_plugin.
Use a section id that does not conflict with an existing section on the same settings tab. For the checkout tab, ids that match existing payment gateway sections are reserved.
The legacy adapter converts the existing get_settings() array into a canonical schema for React. It supports common settings fields:
textpasswordemailurltelnumbertextareacheckboxselectradiomultiselectmulti_select_countriessingle_select_countrysingle_select_pageinfoFields before the first title marker are placed into a default group automatically.
The default save adapter is form_post, which serializes hidden inputs so WC_Admin_Settings::save_fields() continues to save the submitted values.
If a field needs a custom React UI, declare a component name in the PHP field schema:
array(
'id' => 'my_plugin_payment_methods',
'title' => __( 'Payment methods', 'my-plugin' ),
'type' => 'multiselect',
'component' => 'my-plugin/payment-method-picker',
'options' => array(
'card' => __( 'Card', 'my-plugin' ),
'bnpl' => __( 'Buy now, pay later', 'my-plugin' ),
),
)
Then register that component from JavaScript:
import { registerSettingsExtension } from '@woocommerce/settings-ui';
import { PaymentMethodPicker } from './payment-method-picker';
registerSettingsExtension( {
scope: {
page: 'my_plugin',
},
components: {
'my-plugin/payment-method-picker': PaymentMethodPicker,
},
} );
Omit scope.section for a page-wide registration. Use section: '' for the default section only, or pass a section id such as section: 'payments' for one named section.
See Registering settings UI components for the full component contract.
Custom component scripts must load before the settings app mounts. Return their registered WordPress script handles from the adapter:
<?php
use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
final class My_Plugin_Settings_UI_Page extends LegacySettingsPageAdapter {
public function get_script_handles( string $section ): array {
return array( 'my-plugin-settings-ui' );
}
}
The settings embed script depends on the settings UI package and these handles only for the opted-in page. Other settings pages do not load it.
The settings UI supports two save adapters:
| Adapter | Behavior |
|---|---|
form_post | Serializes hidden inputs for the existing WooCommerce settings form. |
none | Does not submit a value. Use for display-only fields. |
The legacy adapter uses form_post by default. A field can override its save behavior:
array(
'id' => 'my_plugin_read_only_notice',
'type' => 'info',
'text' => __( 'This field is display only.', 'my-plugin' ),
'save' => array(
'adapter' => 'none',
),
)
Group title rows can include sanitized description markup and structured header actions. Use this for contextual links such as documentation or secondary actions that belong to the whole group, rather than creating a display-only custom field.
array(
'id' => 'my_plugin_checkout',
'type' => 'title',
'title' => __( 'Checkout experience', 'my-plugin' ),
'desc' => sprintf(
/* translators: %s: documentation link */
__( 'Choose where customers can use express payment methods. %s', 'my-plugin' ),
'<a href="' . esc_url( 'https://example.com/docs' ) . '">' . esc_html__( 'Learn more', 'my-plugin' ) . '</a>'
),
'actions' => array(
array(
'id' => 'manage',
'label' => __( 'Manage locations', 'my-plugin' ),
'href' => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
'variant' => 'secondary',
),
),
)
Descriptions are sanitized with wp_kses_post(). Actions are structured data with id, label, href, optional variant, optional target, and optional rel.
The Products settings page is the Core reference migration. With settings-ui enabled, the Products tab renders through the settings UI. With the flag disabled, it renders through the existing legacy settings UI.
Use this page to verify the native migration path before testing a plugin-specific page such as WooPayments.
settings-ui feature flag.WC_Settings_Page subclass.component metadata only for fields that need custom UI.registerSettingsExtension().get_script_handles() so they load before mount.In development, the settings UI logs warnings for common integration issues:
wc-settings-ui script is missing for a settings UI mount.