2

I'm trying to wrap my head around the Settings and Options APIs as I'm writing a simple plugin.

One thing that has really stomped me is the possibility to add a default value to an option when calling the register_setting() function. This does not work the way I expected and I don't get how it's supposed to work.

Here is a simplified example:

<?php
/**
 * Plugin name: Options test
 */

define( 'MY_PAGE', 'my_test_page' );
define( 'MY_SECTION', 'my_test_section' );
define( 'MY_OPTION', 'my_test_option' );

add_action( 'admin_menu', function () {
    add_options_page( 'Options test', 'Options test', 'manage_options', MY_PAGE, function () {
        echo '<div class="wrap"><h1>Options test</h1>';
        echo '<form action="options.php" method="post">';
        settings_fields( MY_PAGE );
        do_settings_sections( MY_PAGE );
        submit_button();
        echo '</form>';
        echo '<p>The option value: <code>' . get_option( MY_OPTION ) . '</code></p>';
        echo '</div>';
    } );
} );

add_action( 'admin_init', function () {
    register_setting( MY_PAGE, MY_OPTION, [ 'default' => 'My default value' ] );
    add_settings_section( MY_SECTION, '', null, MY_PAGE );
    add_settings_field( MY_OPTION, 'The option', function () {
        echo '<input name="' . MY_OPTION . '" value="' . get_option( MY_OPTION ) . '">';
    }, MY_PAGE, MY_SECTION );
} );

add_shortcode( 'my-test-option', function ( $atts, $content = '' ) {
    return get_option( MY_OPTION );
} );

This results in a setings page that looks like this:

enter image description here

You can see that the <input> has a default value and that the separate call to get_option() returns the default value - on this page. When accessing the option on the front end using the shortcode [my-test-option] it is blank. This is expected when I haven't visited the settings page, as nothing has been saved to the database. There is no 'my_test_option' in the wp_options database table.

But...

Even when I press the [Save Changes] button, the default value is NOT saved to the database. I've traced the code and found that in the update_option() function (/wp-includes/option.php) there is a test to check if the new value is the same as the old, and if so the new value isn't stored.

So in order to actually save the default value to the database using this custom settings page, I would have to change the value to something other than the default, save it, change it back to the default and save it again. Very unintuitive.

I've already understood that I need to fix this by using register_activation_hook() in my plugin and add the default options to the database there. Then remove the default from the register_setting() calls.

What I don't understand is how the default parameter to the register_setting() function is meant to be used. When and how is it useful? As it stands, it has only lead to confusion and wasted hours.

3
  • I have posted an answer, but actually, if the option does not exist in the database, (I thought) it's perfectly fine if the option is not saved when the new value is the same as the default value - because the default value has already been registered in PHP, hence WordPress knows there is and what is the default value - and it's returned when get_option() is called.
    – Sally CJ
    Mar 22, 2022 at 3:49
  • 1
    @SallyCJ Makes perfect sense. I guess my problem was not realizing I should have used register_setting() outside of the function hooked to admin_init that builds the settings form by calling add_settings_field() etc. Mar 22, 2022 at 8:03
  • Yes, that's what I had in mind in the first place.. Anyway, I'm glad my answer helped. :)
    – Sally CJ
    Mar 22, 2022 at 9:36

1 Answer 1

3

Excerpt from the register_setting() documentation:

'default'
(mixed) Default value when calling get_option().

So the purpose of the default argument is to provide a default value which get_option() would return if the option does not exist (or has not yet been saved) in the database.

And register_setting() sets the option's default value via filter_default_option() which is hooked on the default_option_<option name> filter: (see source on GitHub)

if ( array_key_exists( 'default', $args ) ) {
    add_filter( "default_option_{$option_name}", 'filter_default_option', 10, 3 );
}

Therefore, because update_option() uses $old_value = get_option( $option );, i.e. it uses get_option() which runs the above hook, then if the option hasn't yet been saved in the database, the old value would then be the default value and thus the option wouldn't be saved if the new and the default/old values are the same just like you can see in the example below:

$option  = 'my_test_option';
$default = 'My default value';

register_setting( 'my_test_page', $option, [
    'default' => $default,
] );

delete_option( $option ); // JUST FOR TESTING

// So at this point, the option doesn't yet exist.
var_dump( get_option( $option ) );                   // string(16) "My default value"
// this displays a custom value because the 2nd parameter ($default) is specified:
var_dump( get_option( $option, 'a custom value' ) ); // string(14) "a custom value"

// Now let's save the option.
$old_value = get_option( $option );  // this returns "My default value"
var_dump( $old_value === $default ); // bool(true)
// bool(false), because $default is the same as $old_value
var_dump( update_option( $option, $default ) );
// bool(true), because 'foo' is not the same as $old_value
var_dump( update_option( $option, 'foo' ) );

// Retrieve the saved option.
$old_value = get_option( $option );
var_dump( $old_value ); // string(3) "foo"

// Update the option. (set the value to the default value)
var_dump( $old_value === $default ); // bool(false)
// bool(true), because the old value is now 'foo'
var_dump( update_option( $option, $default ) );

As for this: (italic formatting added by me)

When accessing the option on the front end using the shortcode [my-test-option] it is blank. This is expected when I haven't visited the settings page, as nothing has been saved to the database.

Yes, that's expected, and it's because you used the admin_init hook to register the setting on the admin side only (wp-admin).

If you used a hook which runs both on the admin and non-admin sides of the site, e.g. init, then you would've seen the default option value instead of the blank output.

2
  • 1
    This is one impressive answer. Thanks a million. Everything makes perfect sense now. I guess I was led astray by the examples in the documentation which all called register_setting() along with add_settings_field() in the admin_init hook. I wasn't able to think outside the box. Mar 22, 2022 at 7:57
  • Since the Codex specifically says in a nice big callout: "register_setting() as well as the mentioned add_settings_*() functions should all be added to the admin_init action hook." I think you can be forgiven!
    – dtw
    Aug 13, 2022 at 8:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.