Validation and Sanitization in the Customizer

At Automattic, we exclusively use the Customizer for theme options instead of theme option pages. We believe that themes should only alter the display of content and should not add any additional functionality that would be better suited for a plugin. Since all options are presentation centered, they should all be controllable by the Customizer.

If you are not too familiar with how you can use the Customizer for your theme projects, I recommend the three posts Sam “Otto” Wood wrote on the subject. This article assumes a familiarity with its basic concepts.

The Customizer handles everything, from creating theme options’ markup, to saving settings, to updating the theme preview. It does not, however, handle validation and sanitization.

Similar to the Settings API, the Customizer comes with a designated way to validate or sanitize user input. There are two ways to achieve this:

  1. Defining a callback function for each customizer setting.
  2. Hooking into an action prior to saving all settings to the database.

Per Setting Sanitization

The add_setting() method of the WP_Customizer object accepts an 'sanitize_callback' argument, that can be used to specify a sanitization callback. This is how it might look:


function prefix_customize_register( $wp_customize ) {
	$wp_customize->add_section( 'prefix_theme_options', array(
		'title'    => __( 'Theme Options', 'textdomain' ),
		'priority' => 101,
	) );

	$wp_customize->add_setting( 'prefix_layout', array(
		'default'           => 'content-sidebar',
		'transport'         => 'postMessage',
		'sanitize_callback' => 'prefix_sanitize_layout',
	) );

	$wp_customize->add_control( 'prefix_layouts', array(
		'label'    => __( 'Layout', 'textdomain' ),
		'section'  => 'prefix_theme_options',
		'settings' => 'prefix_layout',
		'type'     => 'radio',
		'choices'  => array(
			'content-sidebar' => __( 'Content on left', 'textdomain' ),
			'sidebar-content' => __( 'Content on right', 'textdomain' ),
		),
	) );
}
add_action( 'customize_register', 'prefix_customize_register' );

function prefix_sanitize_layout( $value ) {
	if ( ! in_array( $value, array( 'content-sidebar', 'sidebar-content' ) ) )
		$value = 'content-sidebar';

	return $value;
}

The callback will only be called when the setting is saved to the database. If you use the refresh method to update the preview, sanitization is complete.

In our case, since we opted to use the postMessage method of updating the preview, we would have to validate (and sanitize) the the value in the corresponding JavaScript file, too.

In many cases it is not even necessary to define a custom callback function. In cases where you want to sanitize a URL or an email address for example, you can pass 'esc_url_raw' or 'is_email' directly:


$wp_customize->add_setting( 'prefix_email_address', array(
	'default'           => '',
	'transport'         => 'postMessage',
	'sanitize_callback' => 'is_email',
) );

$wp_customize->add_setting( 'prefix_twitter_url', array(
	'default'           => '',
	'transport'         => 'postMessage',
	'sanitize_callback' => 'esc_url_raw',
) );

All Settings Sanitization

For completeness sake: a second possibility is to use the 'sanitize_option_' . $option hook for theme mods or register_settings() for options.


function prefix_sanitize_customizer( $value ) {
	if ( ! isset( $value['prefix_layout'] ) || ! in_array( $value['prefix_layout'], array( 'content-sidebar', 'sidebar-content' ) ) )
		$value['prefix_layout'] = 'content-sidebar';

	return $value;
}

// For theme mods:
add_action( 'sanitize_option_theme_mods_twentythirteen', 'prefix_sanitize_customizer' );

// For options:
register_setting( 'prefix_option_group', 'prefix_option_name', 'prefix_sanitize_customizer' );

 

I hope you now have a better understanding of how to write more secure code when dealing with the Customizer. It’s a great tool for users and theme developers alike, and I am very excited about the opportunities it creates.

If you have any questions or comments, feel free to add them below!

13 responses

  1. Few points:

    – sanitize_callback and sanitize_js_callback are good, but I believe were added after I wrote those tutorials, which is why I don’t mention them. I should probably do a new writeup about them.

    – If you’re using actual Settings (and not the theme-mods system), then you have the option to specify a $sanitize_callback function name as the third parameter to the register_setting() function call. This callback will be called anytime that option is updated in the database. This includes via the customizer. This is a safer approach than using pre_update_option_$option to deal with it.

    – If you are using theme mods, then hooking to sanitize_option_theme_mods_$theme is probably the better hook to use, because, well, it has sanitize right in the name, and that’s where sanitization stuff is intended to hook in. Although realistically, and for sanity’s sake, I’d suggest using the individual setting’s callbacks in add_setting instead. Safer, less bug-prone, even if it is a bit more typing and such. And as you point out, a lot of those type of sanitization functions you can get for free by using the built in functions.

    1. Thanks so much for your feedback Otto! Nacin told me yesterday about how register_settings() is more appropriate for option sanitization – I never thought of connecting the Settings API and the Customizer like that.

      I’ll update the post with both your suggestions, thanks again!

  2. I have used sanitize_email() for email. Do you think it’s ok to use that or should I use is_email() also?

    1. That is a very good question. I’m not too sure about the difference between the two, but I don’t think it is wrong to use it.

      1. I checked the file wp-includes/formatting.php and it seems that both functions do pretty much the same checking for the email. Naturally is_email() returns false if it doesn’t pass the check and sanitize_email() returns filtered email address.

        So in my opinion both are fine to use.

    2. If Is_What() Then Do_That();

      Use is_email() as conditional tag to sanitize an email adress:

      if ( is_email( $email ) )
      $email = sanitize_email( $email );
      else
      do_error_handling( $email );

      Look at this code:

      $emails = array( 'foo@bar.com', 'baz-at-example-org', 'öttö@wördpäss.com', 3 );
      echo '';
      array_walk(
      $emails,
      function ($email) {
      $is = ( true == is_email( $email ) ) ? 'is email' : 'is not an email';
      $se = sanitize_email( $email );
      printf( '%s -> %s :: %s', $email, $is, ( '' != $se ) ? $se : 'an empty string' );
      }
      );
      echo '';
      /*
      * Output:
      * 1. foo@bar.com -> is email :: foo@bar.com
      * 2. baz-at-example-org -> is not an email :: an empty string
      * 3. öttö@wördpäss.com -> is not an email :: tt@wrdpss.com
      * 4. 3 -> is not an email :: an empty string
      */

      If you execute this code, you can see that öttö@wördpräss.com is not a valid email adress, but sanitize_email() returns … ehmmm… some chars.
      This code would lead you into trouble if you depend on the result by sanitize_email():

      wp_mail( sanitize_email( $email ), 'Testmail', 'A testmessage' );

      The right code would be:

      if ( is_email( $email ) )
      update_option( 'awesome_theme_email', sanitize_email( $email ) );

      if ( is_email( $email ) )
      wp_mail( sanitize_email( $email ), 'Testmail', 'A testmessage' );

      Right after retrieving an email from some input, always check if it is a valid email with is_email() and then sanitize it. Never ever sanitize it first and validate it later.

      1. Valid points, thanks.

        But in theme Customizer we’re not sending any emails, at least I’m not. For me it’s just for gravatar email so I don’t mind if öttö@wp.com gets to tt@wp.com. User will notice this right away in live preview.

        But then again I could check is_email first and if it fails I could place invalid email in text field. That might be even more user friendly.

  3. hockthai Avatar

    Thank you for posting. I learn a lot about validation in the customizer in just 5 minutes.

  4. […] Validation and sanitization in the theme customizer » […]

  5. shawn Avatar

    If you’re using actual Settings (and not the theme-mods system), then you have the option to specify a $sanitize_callback function name as the third parameter to the register_setting() function call. This callback will be called anytime that option is updated in the database. This includes via the customizer. This is a safer approach than using pre_update_option_$option to deal with it.

    Sorry, but I’m a bit unclear here as to whether the sanitize_callback can be used with theme-mods system. This (quoted above) seems to suggest no, but the example(s) code seems to default to the theme_mod since type (type => option) argument in not used in any of these.

    Can you guys please clarify this for me?

    Thanks.

    1. Use of the register_settings() function call is for when you’re using options, not theme_mods.

      If you use theme_mods with the customizer, then the method above where he uses the $wp_customize->add_setting()’s sanitize_callback feature is the more correct method to use.

  6. […] 43. Validation and Sanitization in the Customizer […]

  7. […] Fortunately, the Theme Customizer API provides a way to sanitize Theme Mods option data: the ‘sanitize_callback’ parameter of the add_setting() argument array. Didn’t know about that parameter? Don’t worry; neither did I until recently. It’s so super-secret that it’s not even listed in the add_setting() method Codex entry. Fortunately, Konstantin Obenland discusses it here. […]