Skip to content
ExponaBLOG
WORDPRESS DEVELOPMENT & PLUGINS · DAY 014 / 100

The Options API: what every WordPress developer should know

Store plugin settings.

Expona Admin7 min read

Introduction

Every WordPress plugin needs a way to store settings, and the Options API is the foundation for doing that reliably. Whether you're building a custom post type handler, an email notification system, or a full-featured SaaS integration, understanding what every WordPress developer should know about the Options API will save you from performance headaches and data consistency issues down the road.

The Options API isn't glamorous, but it's the backbone of plugin configuration in WordPress. In this guide, we'll walk through how to use it effectively, when to optimize, and how to clean up after ourselves.

What the Options API actually does

The Options API is a key-value storage system built into WordPress's core. It sits on top of the wp_options table in your database and provides a simple interface for reading and writing plugin settings. Every option gets its own row with a key (the option name), a value (serialized if needed), and metadata like autoload status and timestamps.

The two fundamental functions are straightforward:

  • get_option( $option, $default ) — retrieves a value, returning $default if it doesn't exist
  • update_option( $option, $value ) — creates or updates a value in the database

Behind the scenes, WordPress handles serialization automatically. If you pass an array or object, it gets serialized to a string in the database and unserialized when you retrieve it. This is convenient, but it's also where many developers make their first mistake.

get_option and update_option: the core workflow

Let's start with a real scenario. You're building a plugin that tracks API credentials and rate limits. Your first instinct might be to create separate options for each setting:

php
// Not ideal, but common
update_option( 'my_plugin_api_key', 'abc123' );
update_option( 'my_plugin_api_secret', 'xyz789' );
update_option( 'my_plugin_rate_limit', 1000 );
update_option( 'my_plugin_webhook_url', 'https://example.com/webhook' );

This works, but it's inefficient. Each update_option call triggers a database write. Each get_option call on page load fetches from the database. Over time, your wp_options table grows unnecessarily, and performance suffers.

Here's how to retrieve them:

php
$api_key = get_option( 'my_plugin_api_key' );
$api_secret = get_option( 'my_plugin_api_secret' );
$rate_limit = get_option( 'my_plugin_rate_limit' );
$webhook_url = get_option( 'my_plugin_webhook_url' );

The retrieval works fine, but you're making four separate database queries just to load plugin settings. On a site with dozens of plugins, this compounds quickly.

update_option is smart enough to detect changes. If you call update_option( 'my_key', 'value' ) and 'my_key' already holds 'value', WordPress skips the database write. This is helpful but shouldn't be relied upon as your primary optimization strategy.

Group settings into one array

The fix is to group related settings into a single option containing an array. This is the approach we use across every plugin we ship:

php
// Store settings as one option
$plugin_settings = array(
    'api_key'      => 'abc123',
    'api_secret'   => 'xyz789',
    'rate_limit'   => 1000,
    'webhook_url'  => 'https://example.com/webhook',
);

update_option( 'my_plugin_settings', $plugin_settings );

Now you're writing once to the database instead of four times. Retrieval is equally simple:

php
$settings = get_option( 'my_plugin_settings', array() );

$api_key = $settings['api_key'] ?? '';
$rate_limit = $settings['rate_limit'] ?? 0;

Use the array syntax with null coalescing (??) to avoid undefined key notices. This pattern scales well because you're making one database call instead of many.

For larger plugins, consider namespacing your option keys to avoid collisions:

php
update_option( 'my_plugin_general_settings', $general_settings );
update_option( 'my_plugin_email_settings', $email_settings );
update_option( 'my_plugin_api_settings', $api_settings );

This approach groups related settings while keeping the overall number of options low. Three grouped options is far better than twenty individual ones.

autoload only small, frequently used options

Every option has an autoload flag. When autoload is set to 'yes', WordPress loads that option into memory during the initial database query on every page load. This is convenient for frequently accessed settings but dangerous if misused.

Here's the problem: if you mark a large option as autoload and it's rarely used, you're loading it into memory on every single page load, even admin pages and AJAX requests that never need it. On a high-traffic site, this is wasted memory and slower response times.

The rule is simple: autoload only small, frequently used options. A 50KB serialized array marked as autoload across millions of page views adds up to significant overhead.

When you use update_option, autoload defaults to 'yes'. You need to explicitly set it to 'no' for options that aren't frequently needed:

php
// Frequently accessed, small in size — autoload enabled (default)
$settings = array(
    'api_key'     => 'abc123',
    'rate_limit'  => 1000,
);
update_option( 'my_plugin_settings', $settings );

// Rarely accessed, potentially large — autoload disabled
$large_log = array( /* ... */ );
update_option( 'my_plugin_debug_log', $large_log, 'no' );

// Temporarily cached data — autoload disabled
$cached_data = array( /* ... */ );
update_option( 'my_plugin_cache', $cached_data, 'no' );

The fourth parameter to update_option controls autoload. Use 'yes' (the default) for critical settings, and 'no' for everything else.

You can verify autoload status by checking the wp_options table directly, but the cleanest approach is to be explicit in your code. Document which options are autoloaded and why. This prevents future developers from accidentally marking a 1MB setting as autoload.

Delete options on uninstall

This is where many plugins fail. When a user deactivates your plugin, the settings usually stay in the database forever. If they uninstall it, those orphaned options clutter the wp_options table.

A responsible plugin cleans up after itself. Create a plugin uninstall routine to remove all options your plugin created:

php
// In your main plugin file, register the uninstall hook
register_uninstall_hook( __FILE__, 'my_plugin_uninstall' );

function my_plugin_uninstall() {
    // Remove all plugin options
    delete_option( 'my_plugin_settings' );
    delete_option( 'my_plugin_email_settings' );
    delete_option( 'my_plugin_cache' );
    delete_option( 'my_plugin_debug_log' );
    
    // Remove any user meta associated with your plugin
    delete_metadata( 'user', 0, 'my_plugin_preference', '', true );
}

The uninstall hook only fires when a user clicks "Delete" from the Plugins page, not during deactivation. This is the right moment to clean up.

If you use multisite, you may also want to remove options from other blogs:

php
function my_plugin_uninstall() {
    global $wpdb;
    
    if ( is_multisite() ) {
        $blogs = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
        foreach ( $blogs as $blog_id ) {
            switch_to_blog( $blog_id );
            delete_option( 'my_plugin_settings' );
            restore_current_blog();
        }
    } else {
        delete_option( 'my_plugin_settings' );
    }
}

This pattern iterates through every site on the network and removes your plugin's options from each one.

Common mistakes

  • Creating too many individual options — This bloats the wp_options table and increases database queries on every page load. Group related settings.
  • Autoloading large or infrequently used options — Mark non-critical options with 'no' to reduce memory consumption and page load time.
  • Forgetting to sanitize and validate — Always sanitize input with functions like sanitize_text_field() and wp_kses_post() before calling update_option(). Validate that incoming data matches your expected format.
  • Not handling missing options gracefully — Always provide a sensible default. Use get_option( 'my_key', array() ) to avoid undefined index errors.
  • Neglecting to uninstall properly — Leaving orphaned options in the database is unprofessional and slows down WordPress over time. Always implement an uninstall routine.
  • Storing sensitive data without encryption — API keys and secrets should be encrypted before storage. Consider using a dedicated secrets management solution for production sites.

Key takeaways

  • Use get_option() and update_option() as the primary interface to the Options API, always providing sensible defaults.
  • Group related settings into a single array-based option instead of creating dozens of individual options.
  • Set autoload to 'no' for options that aren't frequently accessed or are large in size to reduce memory consumption.
  • Implement a proper uninstall routine to clean up all plugin options when the plugin is deleted, especially on multisite installations.
  • Always sanitize and validate option values before storing them, and document which options are autoloaded and why.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles