Create a settings page in WordPress with React – Part 2
In the first part of this tutorial, I showed the basic principles to create a settings page in WordPress, in this second part, I will show you how to use the components included in WordPress to add text fields, and save the options.
Table of Contents
- Step 1: Creating our application
- Step 2: Add some options
- Step 3: Register options in the backend
- Step 4: Add save button with React
You may need to install some packages for this tutorial. Although thanks to our compiler in @wordpress/scripts that automatically adds them to the dependencies of the script it may not be necessary, I prefer to install them simply to have a record of them.
If you happen to have an error simply run npm install --save-dev {package}
with the following packages:
@wordpress/components
@wordpress/element
@wordpress/i18n
Step 1: Creating our application
To create a settings page, we need to add some fields. To start, we must open the
settings.js
file where we will start our application, and create a simple one:
settings.js
1/**2 * WordPress dependencies3 */4import { createRoot } from '@wordpress/element';56/**7 * Internal dependencies8 */9import Settings from './components/settings';1011const rootElement = document.querySelector('#mah-settings');1213if (rootElement) {14 createRoot(rootElement).render(<Settings />);15}16
components/settings.js
1/**2 * WordPress dependencies3 */4import { __ } from '@wordpress/i18n';56const Settings = () => {7 return <h1>{__('Select the desired options', 'mah-settings')}</h1>;8};910export default Settings;11
Among the things we can observe in this piece of code are:
- The use of
createRoot
with which we start our applications in React, but unlike how it is normally used, this time we take it from@wordpress/element
. This is because WordPress already comes with its own copy of React that ensures that its components will work. - Likewise, it comes with a javascript version of the
__
function for translations, unlike PHP there is no version ofesc_html__
so we use the normal version.
Using React
As you can see from this small piece of code (and the title of the post...). This tutorial assumes that you have at least a basic knowledge of ReactJS, if not; I invite you to study it a bit so you don't lose the thread of this topic.
Step 2: Add some options
Now that we have our application running we can start to add options to our page, one of the most common I come across is text fields, perhaps for an API, for example:
1/**2 * WordPress dependencies3 */4import { Card, CardBody, TextControl } from '@wordpress/components';5import { useState } from '@wordpress/element';6import { __ } from '@wordpress/i18n';78const Settings = () => {9 const [apiKey, setApiKey] = useState('');1011 return (12 <>13 <h1>{__('Select the desired options', 'mah-settings')}</h1>14 <Card>15 <CardBody>16 <TextControl17 help={__('Enter your API key', 'mah-settings')}18 label={__('API Key', 'mah-settings')}19 onChange={setApiKey}20 value={apiKey}21 />22 </CardBody>23 </Card>24 </>25 );26};2728export default Settings;29
After reloading the page, we can see that our options page takes shape! although we can add some flavor, but we will do that later, now that we have our field, let's explain what happens here:
- Some more WordPress components are added, this time they are
Card
,CardBody
, andTextControl
. In reality the important one is TextControl since it is the one we will use to enter our options - We import
useState
from@wordpress/element
(once again, React comes included) - We add an empty state that will be updated when we change something in
TextControl
- The use of React fragments, only that we use its shortened version, instead
of
import { Fragment } from '@wordpress/element'
we simply use<></>

@wordpress/components
comes with many components available, I suggest
checking the Storybook
to see a demonstration of what we have at our disposal
As a bonus, let's add a toggle
simply because I like how they work:
1/**2 * WordPress dependencies3 */4import {5 Card,6 CardBody,7 CardDivider,8 TextControl,9 ToggleControl,10} from '@wordpress/components';11import { useState } from '@wordpress/element';12import { __ } from '@wordpress/i18n';1314const Settings = () => {15 const [ apiKey, setApiKey ] = useState( '' );16 const [ toggled, setToggled ] = useState( false );1718 return (19 <>20 <h1>{ **( 'Select the desired options', 'mah-settings' ) }</h1>21 <Card>22 <CardBody>23 <TextControl24 help={ **( 'Enter your API key', 'mah-settings' ) }25 label={ __( 'API Key', 'mah-settings' ) }26 onChange={ setApiKey }27 value={ apiKey }28 />29 </CardBody>30 <CardDivider />31 <CardBody>32 <ToggleControl33 label={ __( 'Activate functionality', 'mah-settings' ) }34 onChange={ setToggled }35 checked={ toggled }36 />37 </CardBody>38 </Card>39 </>40 );41}42export default Settings;43
What happened?
If we reload the page, we realize that our toggle
looks more like a checkbox, what happened?

What happens is that despite our javascript dependencies being automatically added to our script,
the same does not happen with our style dependencies. So it may be necessary to add them ourselves to the file
class-mah-settings.php
in a similar way to what we did with the javascript script:
class-mah-settings.php
1/**2 * Registra los scripts y estilos.3 *4 * @return void5 */6public function enqueue_scripts(): void {7 $screen = get_current_screen();89 if ( $screen->id !== $this->menu_slug ) {10 return;11 }1213 $asset = include $this->plugin_path . '/build/settings.asset.php';1415 wp_enqueue_script(16 'mah-settings',17 plugins_url() . '/mah-settings-react/build/settings.js',18 $asset['dependencies'],19 $asset['version'],20 true21 );2223 wp_enqueue_style( 'wp-components' );24}25
When we reload the page we will see the desired result, and if we look closely, our text field also improved in appearance:

Step 3: Register options in the backend
To be able to save our options, we need to have values in the database where to save them, WordPress basically handles the following types:
In our case we will use global options, due to the nature of our options page.
In php, we can register them like this:
php/class-mah-settings.php
1/**2 * Registra los hooks.3 *4 * @return void5 */6public function init(): void {7 add_action( 'admin_menu', [ $this, 'add_menu_page' ] );8 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );9 add_action( 'admin_init', [ $this, 'register_settings' ] );10 add_action( 'rest_api_init', [ $this, 'register_settings' ] );11}12
php/class-mah-settings.php
1/**2 * Registra las opciones.3 *4 * @return void5 */6public function register_settings(): void {7 register_setting(8 'mah_settings',9 'mah_api_key',10 [11 'description' => __( 'API Key', 'mah-settings' ),12 'sanitize_callback' => 'sanitize_text_field',13 'show_in_rest' => true,14 'type' => 'string',15 ]16 );17 register_setting(18 'mah_settings',19 'mah_function',20 [21 'description' => __( 'Funcion X', 'mah-settings' ),22 'show_in_rest' => true,23 'type' => 'boolean',24 ]25 );26}27
The second rest_api_init hook is necessary to be able to use our options in React,
otherwise show_in_rest
would have no effect
Step 4: Add save button with React
Now we just need to save our options from the React side, for which we need to add a save button to our application, and add an event when it is pressed:
1/**2 * WordPress dependencies3 */4import {5 Button,6 Card,7 CardBody,8 CardDivider,9 CardFooter,10 TextControl,11 ToggleControl,12} from '@wordpress/components';13import { useState } from '@wordpress/element';14import { __ } from '@wordpress/i18n';1516const Settings = () => {17 const [apiKey, setApiKey] = useState('');18 const [toggled, setToggled] = useState(false);19 const saveSettings = () => {20 console.log('Save');21 };22 return (23 <>24 <h1>{__('Select the desired options', 'mah-settings')}</h1>25 <Card>26 <CardBody>27 <TextControl28 help={__('Enter your API key', 'mah-settings')}29 label={__('API Key', 'mah-settings')}30 onChange={setApiKey}31 value={apiKey}32 />33 </CardBody>34 <CardDivider />35 <CardBody>36 <ToggleControl37 label={__('Activate functionality', 'mah-settings')}38 onChange={setToggled}39 checked={toggled}40 />41 </CardBody>42 <CardFooter>43 <Button disabled={!apiKey} onClick={saveSettings} variant='primary'>44 {__('Save', 'mah-settings')}45 </Button>46 </CardFooter>47 </Card>48 </>49 );50};51export default Settings;52
A brief explanation of what we just added:
- We bring a new component
CardFooter
, which will help us maintain the style of our card - We bring the
Button
component that comes with WordPress, this gives us some advantages over using regular buttons, such as the fact that it already comes with styles for example, this time we use theprimary
variant. And we make sure that it remains disabled if the API key field has not been filled - We add the
saveSettings
function that will be in charge of saving our options
So when we click the button we will see how our action is registered in the console, now we just need to replace it with the real save function.

Coming soon…
My original idea was to finish the tutorial in this part, but I realized how long the post is becoming and there are still quite a few concepts I want to touch on, so I have decided that this is a good time to finish this post and return next week with a third part where we will save our options, finish creating a settings page; and we will see concepts like:
- The use of
useDispatch
,useEntityProp
, andsaveEditedEntityRecord
- UX improvements for our page
- General introduction to the
@wordpress/data
package and the use of stores.
In the meantime you can check the code of this second part in my repository.
As always your comments and suggestions on what you would like to see explained in this humble blog are welcome. Until next time!
Read more
Footnotes
-
Very popular in the days of customizer, theme mods are tied to the theme used on our site, if the theme changes, the mods also disappear, but they return when the theme they are tied to is selected again. But with the arrival of FSE, these begin to be irrelevant in my opinion ↩︎ ↩
-
Post meta, are basically the attributes that are tied to a specific post, these can change from post to post ↩︎ ↩
-
Used for global options that can be used by either a theme or a plugin, regardless of which post or theme is selected on the site ↩︎ ↩