docs/extensions/settings-and-config/registering-settings-ui-components.md
Use custom components when a WooCommerce settings field needs plugin-specific React UI that cannot be represented by a native field type.
For most fields, prefer the native renderer. Custom components are best for specialized selectors, previews, or validation flows.
Declare a stable component name on the field:
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' ),
),
)
The component value is a name, not a script handle. It lets the PHP schema say which renderer a field intends to use while JavaScript supplies the implementation.
Register components with registerSettingsExtension() from @woocommerce/settings-ui-sdk:
import { registerSettingsExtension } from '@woocommerce/settings-ui-sdk';
import { PaymentMethodPicker } from './payment-method-picker';
registerSettingsExtension( {
scope: {
page: 'my_plugin',
section: 'payments',
},
components: {
'my-plugin/payment-method-picker': PaymentMethodPicker,
},
} );
Registrations are scoped by settings page and, optionally, by section. This prevents one plugin from accidentally replacing another plugin's field behavior.
Custom components receive stable field props:
type SettingsFieldComponentProps = {
field: {
id: string;
label: string;
type: string;
description?: string;
value?: string | number | boolean | string[] | null;
options?: Array< { label: string; value: string } >;
component?: string;
placeholder?: string;
disabled?: boolean;
customAttributes?: Record< string, string | number | boolean >;
};
value: string | number | boolean | string[] | null;
onChange: ( value: string | number | boolean | string[] | null ) => void;
context: {
page: string;
section?: string;
};
};
Call onChange() with the next field value. The SDK handles hidden input serialization for the field's save adapter.
import type { SettingsFieldComponentProps } from '@woocommerce/settings-ui-sdk';
export const PaymentMethodPicker = ( {
field,
value,
onChange,
}: SettingsFieldComponentProps ) => {
const selectedValues = Array.isArray( value ) ? value : [];
return (
<fieldset>
<legend>{ field.label }</legend>
{ field.options?.map( ( option ) => {
const checked = selectedValues.includes( option.value );
return (
<label key={ option.value }>
<input
type="checkbox"
checked={ checked }
onChange={ () => {
onChange(
checked
? selectedValues.filter(
( item ) =>
item !== option.value
)
: [ ...selectedValues, option.value ]
);
} }
/>
{ option.label }
</label>
);
} ) }
</fieldset>
);
};
If a legacy field cannot add component metadata directly, register a field override by field id:
registerSettingsExtension( {
scope: {
page: 'my_plugin',
},
fieldOverrides: {
my_plugin_payment_methods: PaymentMethodPicker,
},
} );
Field overrides are useful during migration, but component metadata is preferred because the intended renderer stays close to the PHP field schema.
Use typeRenderers when every field of a type should share the same renderer within a page scope:
registerSettingsExtension( {
scope: {
page: 'my_plugin',
},
typeRenderers: {
my_plugin_color: ColorField,
},
} );
Resolution order is:
field.componentfieldOverrides[ field.id ]typeRenderers[ field.type ]Register your script in WordPress and return its handle from the settings UI 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' );
}
}
WooCommerce loads the SDK first, then your script, then mounts the settings app.