Create a Custom Settings Page in WordPress

I can’t count how many custom settings pages I created in WordPress over the years, not only for clients but for my own projects as well.

Still, this morning, I struggled, because my options just wouldn’t save. So I decided to write down all the parts, that make a working settings page.

It is not a complete example/guide, just a very basic reference for my future self.

Step 1: Register your setting(s)

Define the option group and give your option a name and also add a callback for validation.1

function my_register_settings() {
    register_setting( 'my_settings_group', 'my_settings', [ 'sanitize_callback' => 'my_settings_validate' ] );
}

add_action( 'admin_init', 'my_register_settings' );

Register Setting Function

Step 2: Validate your setting(s)

function my_settings_validate( $args ) {
    // Validate & sanitize input
    return $args;
}

Make sure all input is valid – also sanitize everything before passing it along, as it gets saved to the database after that.

Step 3: Set capability (optional)

Define the capability that is needed to change/save your option. By default this is set to manage_options.

function my_settings_capability( $capability ) {
    return 'edit_pages';
}

add_filter( 'option_page_capability_my_settings_group', 'my_settings_capability' );

Option Page Capability Hook

Step 4: The form

Here’s where I made a mistake: I didn’t point the form’s action attribute to options.php – which is required if you want sanitize and save your input automatically.

So my (now working) form looked something like this:

<form action="options.php">
<?php

settings_errors();

$default_options = [
// Default option values
];

$options = wp_parse_args( get_option( 'my_settings' ), $default_options );
?>
<!-- Input fields, select boxes, submit, etc. -->
</form>

Settings Errors Function
Parse Arguments Function

I hope this is somewhat helpful. I know it will be for me, the next time I create a settings page.


  1. I realized for the first time that the third parameter is not meant as a function callback to validate your setting, but rather has multiple arguments. I still think it is perfectly fine to use the “sanitize_callback” to also do validation at this point. (Please correct my if I am wrong.) ↩︎