wppaste
WordPress

delete_option( string $option ): bool

Since
1.2.0
Source
wp-includes/option.php:1202

Deletes a single row from the wp_options table by option name, returning true only when a matching row existed and was removed. Internal options guarded by wp_protect_special_option() cannot be deleted this way. Pair it with add_option() or update_option() when you need to reset a setting rather than remove it outright.

Removes an option by name. Prevents removal of protected WordPress options.

Compatibility

WordPress
since 1.2.0
PHP
7.4–8.6-dev
  • 6.7.7
  • 6.8.8
  • 6.9.7
  • 7.0.4
  • 7.1.0

Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.

Parameters

$optionstring
Name of the option to delete. Expected to not be SQL-escaped.

Return value

bool
True if the option was deleted, false otherwise.

Code examples

Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.

Delete a custom plugin setting and confirm it's gone

A plugin stores a settings array under its own option name and needs to remove it during cleanup, for example on uninstall.

add_option( 'my_plugin_settings', array( 'enabled' => true, 'threshold' => 10 ) );

$deleted = delete_option( 'my_plugin_settings' );

printf( 'delete_option() returned: %s' . "\n", $deleted ? 'true' : 'false' );

$value = get_option( 'my_plugin_settings', 'default (not found)' );
printf( 'get_option() now returns: %s', esc_html( is_array( $value ) ? print_r( $value, true ) : $value ) );

add_option() is used here purely to have something to remove; in real plugin code the option would already exist from earlier use.

Run cleanup code when a specific option is deleted

Hook into the dynamic delete_option_{$option} action to react whenever one particular option name is removed.

add_option( 'my_feature_flag', '1' );

add_action( 'delete_option_my_feature_flag', function ( $option ) {
	printf( 'Fired delete_option_%s just before removal.' . "\n", esc_html( $option ) );
} );

$result = delete_option( 'my_feature_flag' );

printf( 'Deletion result: %s', $result ? 'true' : 'false' );

The dynamic hook only fires when a matching row is found and the deletion query runs, not when the option was already missing.

Common problems and fixes · 4

Why does delete_option keep returning false even though I'm sure the option exists?

The function first runs a SELECT for the option row and returns false immediately if it comes back null, before any DELETE ever runs. A mismatched option name (wrong case, extra characters, or a name that was never saved) produces exactly this result.

Why did my code die with a 'protected WP option' message when I called delete_option?

delete_option() calls wp_protect_special_option( $option ) before touching the database, and that function stops execution for a small set of internal option names that WordPress manages itself.

Does delete_option clear the object cache right away or just the database row?

Both, but how it updates the cache depends on the option's autoload value. If the option was part of the autoloaded set, delete_option() rebuilds the cached alloptions array and removes the entry; otherwise it calls wp_cache_delete() directly, and either way it marks the name in the notoptions cache so a later get_option() call knows not to query the database again.

Why doesn't my delete_option_{$option} callback fire during install?

The function checks wp_installing() and skips the cache-bookkeeping block entirely while a site is being installed, though the deletion itself and the surrounding delete_option, delete_option_{$option}, and deleted_option actions still fire as long as a matching row was found.

Alternatives and related functions

update_option
When you want to change an option's value without ever removing the row, since update_option() will create it if it doesn't exist yet.
add_option
When you only want to set a value the first time and leave an existing option untouched.
delete_site_option
When the setting is a network-wide option in a multisite install rather than a single site's option.
delete_transient
When the value you're removing was stored as a transient rather than a permanent option, since transients use a separate storage and expiration mechanism.

Performance profile

How much work a call to delete_option() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.

Cost class
Heavy

Reaches the database via ->get_row().

Scaling
Constant

No loop in the body: the same number of instructions runs whatever you pass in.

Instructions
7–87

Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 94.

Plugin surface
3 hooks

Third-party callbacks on 'delete_option', 'delete_option_{$option}', 'deleted_option' run inside this call, and their cost is not bounded by anything here.

Called by
34

34 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • optionoption read or writedelete_option()this function does it
  • hookthird-party callbacksdo_action()called directly
  • cacheobject cachewp_cache_set()called directly
  • sqldatabase query->get_row()called directly

Further down the call graph this can also reach serialize, query and transient. Those are the worst case, several calls deep and usually down an error path, not what a normal call pays.

What one call costs · 10 distinct outcomes

One number would be a lie: the work depends on which branch runs. These are every distinct cost delete_option() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
empty($option)7–9none
!empty($option) && $row === null24–26wp_protect_special_option(), ->prepare(), ->get_row()
!empty($option) && $row !== null && wp_installing()40–42wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing()
!empty($option) && $row !== null && wp_installing()49–51wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), do_action(), do_action()
!empty($option) && $row !== null && !wp_installing()64–67wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_cache_delete(), wp_cache_get(), wp_cache_set()
!empty($option) && $row !== null && !wp_installing()66–71wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_load_alloptions(), wp_cache_get(), wp_cache_set()
!empty($option) && $row !== null && !wp_installing()73–76wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_cache_delete(), wp_cache_get(), wp_cache_set(), do_action(), do_action()
!empty($option) && $row !== null && !wp_installing() && is_array($alloptions) && isset($alloptions[$option])75–78wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_load_alloptions(), wp_cache_set(), wp_cache_get(), wp_cache_set()
!empty($option) && $row !== null && !wp_installing()75–80wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_load_alloptions(), wp_cache_get(), wp_cache_set(), do_action(), do_action()
!empty($option) && $row !== null && !wp_installing() && is_array($alloptions) && isset($alloptions[$option])84–87wp_protect_special_option(), ->prepare(), ->get_row(), do_action(), option_name(), wp_installing(), wp_autoload_values_to_autoload(), wp_load_alloptions(), wp_cache_set(), wp_cache_get(), wp_cache_set(), do_action(), do_action()

Across PHP versions

PHPCompiledExecutedBranchesNotes
8.6-dev947–879
8.5947–879
8.4947–8795 fewer instructions than PHP 8.3
8.3997–929
8.2997–929
8.1997–9292 fewer instructions than PHP 7.4
7.41019–949

An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.

Hooks and filters fired · 3

3 hooks fire while delete_option() runs, in this order:

  1. do_action( delete_option )actionline 1228 (+26 into the body)

    Fires immediately before an option is deleted.

  2. do_action( delete_option_{$option} )actionline 1265 (+63 into the body)

    Fires after a specific option has been deleted.

  3. do_action( deleted_option )actionline 1274 (+72 into the body)

    Fires after an option has been deleted.

Uses · 8

Used by · 34

Show all 34

Source code

function delete_option( $option ) {	global $wpdb; 	if ( is_scalar( $option ) ) {		$option = trim( $option );	} 	if ( empty( $option ) ) {		return false;	} 	wp_protect_special_option( $option ); 	// Get the ID, if no ID then return.	$row = $wpdb->get_row( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) );	if ( is_null( $row ) ) {		return false;	} 	/**	 * Fires immediately before an option is deleted.	 *	 * @since 2.9.0	 *	 * @param string $option Name of the option to delete.	 */	do_action( 'delete_option', $option ); 	$result = $wpdb->delete( $wpdb->options, array( 'option_name' => $option ) ); 	if ( ! wp_installing() ) {		if ( in_array( $row->autoload, wp_autoload_values_to_autoload(), true ) ) {			$alloptions = wp_load_alloptions( true ); 			if ( is_array( $alloptions ) && isset( $alloptions[ $option ] ) ) {				unset( $alloptions[ $option ] );				wp_cache_set( 'alloptions', $alloptions, 'options' );			}		} else {			wp_cache_delete( $option, 'options' );		} 		$notoptions = wp_cache_get( 'notoptions', 'options' ); 		if ( ! is_array( $notoptions ) ) {			$notoptions = array();		}		$notoptions[ $option ] = true; 		wp_cache_set( 'notoptions', $notoptions, 'options' );	} 	if ( $result ) { 		/**		 * Fires after a specific option has been deleted.		 *		 * The dynamic portion of the hook name, `$option`, refers to the option name.		 *		 * @since 3.0.0		 *		 * @param string $option Name of the deleted option.		 */		do_action( "delete_option_{$option}", $option ); 		/**		 * Fires after an option has been deleted.		 *		 * @since 2.9.0		 *		 * @param string $option Name of the deleted option.		 */		do_action( 'deleted_option', $option ); 		return true;	} 	return false;}

Changelog

Introduced in 1.2.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

About this page

Parsed data
Generated from the wordpress-develop 6.9.7 tag, from src/wp-includes/option.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.