WordPress plugin settings are stored in your site’s database, not in the plugin’s files. Nearly every plugin writes its configuration to the wp_options table, usually as a single serialized array under one option name. The rest is scattered across postmeta, usermeta, custom tables, transients, and occasionally files in wp-content/uploads/.
wp_options first. A well-behaved plugin stores everything in one row whose option_name matches its slug, for example wpseo or woocommerce_store_address. Find it with SELECT option_name FROM wp_options WHERE option_name LIKE '%pluginslug%'; Per-post settings live in wp_postmeta, per-user settings in wp_usermeta, and bigger plugins add their own tables prefixed with your table prefix.This matters more than it sounds. It is why deactivating a plugin never loses your configuration, why deleting one often leaves rows behind, why a migration can carry settings across and a manual copy of the plugin folder cannot, and why one badly written option can slow down every page on your site. Here is the full map.
The short version: wp_options does most of the work
The Options API is WordPress’s general-purpose key-value store. A plugin calls add_option(), update_option() and get_option(), and WordPress handles the SQL. Each option becomes one row in wp_options with four columns: option_id, option_name, option_value and autoload.
Two conventions dominate. Some plugins use one row containing an array of every setting, which is tidy and fast. Others use one row per setting, which produces dozens of rows sharing a prefix. WooCommerce is the classic example of the second style, with rows like woocommerce_currency and woocommerce_default_country.
Neither convention is documented anywhere the plugin user can see, which is why you search rather than guess.
Every place a plugin can put your settings
| Storage location | What typically lives there | How to inspect it |
|---|---|---|
wp_options | Global settings, licence keys, API credentials, version numbers, dismissed notices, install timestamps | phpMyAdmin, or wp option get <name> |
wp_postmeta | Per-post fields: SEO titles, page builder layouts, product attributes, form settings | wp post meta list <id> |
wp_usermeta | Per-user preferences, hidden columns, onboarding state, membership levels | wp user meta list <id> |
wp_termmeta | Category and tag extras: SEO overrides, category images, product category thumbnails | wp term meta list <taxonomy> <id> |
| Custom tables | Form entries, analytics events, redirects, order lookup data, licence logs | wp db tables or the phpMyAdmin table list |
| Transients | Cached API responses, update checks, temporary counters. Never real settings. | wp transient list |
Files in wp-content/uploads/ | Generated CSS, cache folders, log files, exported CSVs, GeoIP databases | cPanel File Manager or SFTP |
wp-config.php constants | Occasional API keys and licence keys defined by the site owner, not the plugin UI | Open the file directly |
wp_options at all. They go into wp_sitemeta via get_site_option(), while each subsite keeps its own options table (wp_2_options, wp_3_options, and so on). If a setting seems to have vanished on a network, check wp_sitemeta before you panic.How to find a specific plugin’s option rows
Start with a wildcard search on the plugin slug. If the plugin folder is wp-rocket, try rocket. Slugs and option prefixes often differ, so search the shortest distinctive fragment.
SELECT option_id, option_name, LENGTH(option_value) AS bytes, autoload
FROM wp_options
WHERE option_name LIKE '%rocket%'
ORDER BY bytes DESC;Run that in phpMyAdmin’s SQL tab, or from the command line where the equivalent is shorter:
wp option list --search="*rocket*" --fields=option_name,size_bytes,autoload
wp option get wp_rocket_settings --format=jsonTwo gotchas. The underscore is a single-character wildcard in SQL LIKE, so LIKE 'wp_rocket%' also matches wpxrocket. Harmless when you are reading, dangerous when you are deleting. And if your install uses a randomized table prefix, replace wp_options with the real table name from $table_prefix in wp-config.php. If any of that syntax is new to you, our guide to running a database query in WordPress walks through all four safe ways to do it.
How to read a serialized value
Open an option row and you will often see something like this:
a:3:{s:9:"cache_ttl";i:3600;s:7:"api_key";s:8:"ab12cd34";s:6:"active";b:1;}That is PHP serialization, and it reads mechanically. a:3 is an array with three pairs. s:9:"cache_ttl" is a nine-character string key. i:3600 is an integer, b:1 is boolean true, and d: would be a float. Nested arrays nest the same syntax.
The length prefix on every string is the part that bites people. It is a byte count, not a character count, and PHP refuses to unserialize a string whose prefix is wrong by even one. So do not hand-edit these values in phpMyAdmin. Use wp option patch update <option> <key> <value>, or a tiny PHP snippet that calls get_option(), changes the array, and calls update_option(). WordPress serializes for you and gets the lengths right every time.
wp option get <option_name> --format=json and pipe it through a formatter. WP-CLI unserializes it for you, which turns an unreadable blob into a normal nested object.Autoload: why one option can slow down every page
The autoload column decides whether an option is fetched on every single request. WordPress runs one query at bootstrap that pulls every autoloaded option into memory, so get_option() on those is free afterwards. That is a good trade for a 200-byte setting. It is a terrible trade for a two-megabyte cached API response that one admin page needs once a week.
Sites that have accumulated years of plugins routinely carry several megabytes of autoloaded options, and every page view pays for all of it. Find the offenders:
SELECT option_name,
ROUND(LENGTH(option_value) / 1024, 1) AS kb,
autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
ORDER BY LENGTH(option_value) DESC
LIMIT 25;
SELECT ROUND(SUM(LENGTH(option_value)) / 1024, 1) AS total_kb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');Anything over about 800KB of total autoloaded data is worth investigating. The WP-CLI shortcut is wp option list --autoload=on --format=total_bytes.
Those four values in the IN clause are not redundant. WordPress 6.6 expanded the autoload column from a simple yes/no to five states: on and off when a developer sets it explicitly, auto when nothing is specified, and auto-on or auto-off when WordPress decides for itself. The legacy yes and no values were never migrated, so most established sites hold a mix of old and new values in the same column.
The reason WordPress now decides for itself: options larger than 150,000 bytes are not autoloaded by default, a threshold you can change with the wp_max_autoloaded_option_size filter. That protects you from the worst offenders written after 6.6, but it does nothing about the bloated rows already sitting in your database.
Transients are not settings
Transients are cached data with an expiry, and without a persistent object cache they land in wp_options as two rows per item: _transient_yourkey holding the value and _transient_timeout_yourkey holding a Unix timestamp. Site-wide versions use _site_transient_ instead.
Two details worth knowing. A transient set with no expiry is autoloaded; one set with an expiry is not. And WordPress only cleans up expired transients infrequently, so a plugin that writes a new cache key on every request can leave tens of thousands of dead rows behind. If your wp_options table has 40,000 rows, transients are almost certainly why. Clear them with wp transient delete --expired, which is safe to run any time. If you install a persistent object cache, transients bypass the database entirely and live in memory instead.
What happens on deactivate versus delete
Deactivating a plugin removes it from the active_plugins option and fires its deactivation hook. Settings are untouched by design, so a deactivate-then-reactivate cycle is non-destructive. That is also why disabling a plugin from cPanel when you are locked out of wp-admin costs you nothing but the downtime.
Deleting a plugin runs its uninstall routine, if it has one. WordPress looks for an uninstall.php file in the plugin folder, or for a callback registered with register_uninstall_hook(). Plenty of plugins ship neither, and plenty of the ones that do only clean up their main option row while leaving postmeta and custom tables in place. Some deliberately keep everything so that reinstalling restores your setup, and several offer a “remove all data on uninstall” checkbox buried in their settings, which is worth ticking before you delete something for good.
The practical consequence: leftovers accumulate. A site that has been through fifteen SEO plugins, three page builders, and a couple of analytics integrations will be carrying option rows and postmeta keys from all of them. It is rarely a performance problem unless the leftovers are autoloaded or numerous, but it makes the database harder to reason about.
How to export and migrate settings properly
Copying the plugin folder to a new site moves the code and nothing else. The settings are in the database, so you need one of these:
- The plugin’s own export. Most serious plugins have an import/export screen that produces a JSON file. Always the safest option, because the plugin knows which rows matter.
- WP-CLI, option by option.
wp option get wpseo --format=json > wpseo.jsonon the source, thenwp option update wpseo --format=json < wpseo.jsonon the target. Precise, scriptable, and it never touches serialization by hand. - A full database migration. Move everything, then run
wp search-replace 'https://old.com' 'https://new.com', which rewrites URLs inside serialized values correctly and fixes the length prefixes as it goes.
UPDATE ... REPLACE() on option_value. If the old and new strings differ in length and the value is serialized, you silently corrupt every affected row. The site keeps working until something calls that option, then it fails in a way that is very hard to trace. Use WP-CLI’s search-replace or a serialization-aware plugin instead.Migrating settings is also the reason a local copy is so useful. Build the site locally, get every plugin configured exactly how you want it, then push the whole database up once. Our walkthrough on installing WordPress on a local host covers the push-live step in detail.
Cleaning up orphaned rows safely
Back up the database first. Every single time. Then work in this order, checking the site between steps.
- Clear expired transients with
wp transient delete --expired. Zero risk, often the biggest win. - Fix autoload on any large option you have identified as unnecessary on the front end, rather than deleting it:
UPDATE wp_options SET autoload = 'off' WHERE option_name = 'some_huge_option'; - Delete option rows only for plugins you are certain are gone for good, and list them before you delete them:
wp option list --search="deadplugin_*" --field=option_name, read the output, then pipe it towp option delete. - Drop custom tables last, and only after confirming which plugin owns them. An unrecognized table is not automatically junk; it might belong to something you still use.
Never delete rows whose names you do not recognize just because they look odd. WordPress core itself stores cron, rewrite_rules and a pile of theme mods in wp_options, and losing those causes real problems.
Frequently asked questions
Where are WordPress plugin settings stored in the database?
In the wp_options table, almost always. Look for a row whose option_name contains the plugin slug. Per-post, per-user and per-term settings live in wp_postmeta, wp_usermeta and wp_termmeta, and larger plugins create their own tables sharing your table prefix.
Do plugin settings live in the plugin folder?
No. The folder in wp-content/plugins holds code only. That is why renaming or deleting the folder never loses your configuration, and why copying the folder to another site brings across none of it. A few plugins do write generated CSS or log files into wp-content/uploads, but those are artifacts, not settings.
Why does my wp_options table have thousands of rows?
Expired transients, nearly always. Plugins that cache per-request data without cleaning up can leave tens of thousands of _transient_ pairs behind. Run wp transient delete --expired, then check whether any single plugin keeps recreating them and consider a persistent object cache.
Will deactivating a plugin erase its settings?
No. Deactivation only removes the plugin from the active_plugins option and fires the deactivation hook. Every setting stays exactly where it was, so reactivating restores your configuration. Deleting is the action that may trigger cleanup, and only if the developer wrote an uninstall routine.
How do I find which plugin created an unknown database table?
Search the plugin folder for the table name: the plugin that owns it will reference it in code, usually alongside $wpdb->prefix. Failing that, search the table name plus “WordPress plugin” on the web. Do not drop a table you cannot attribute.
Can I copy just one plugin’s settings between sites?
Yes, if you know the option name. Export it with wp option get <name> --format=json and import with wp option update <name> --format=json. Check the plugin’s own export screen first, though, because settings split across several rows or into postmeta will not travel with a single option.
Wrapping up
Search wp_options for the plugin slug and you will find what you are looking for nine times out of ten. When you do not, the sequence is postmeta, then usermeta, then the custom table list. Read serialized values with WP-CLI rather than by eye, and never edit them by hand in phpMyAdmin.
The one thing worth doing today even if nothing is broken: check your total autoloaded option size. It takes one query, and on a site that has been running for a few years it is often the single cheapest performance fix available. For the underlying detail, WordPress documents the Options API and the autoload changes introduced in 6.6. If you are tracking down a plugin that writes settings you did not expect, our guides to adding Google Analytics to WordPress and installing Google Tag Manager on WordPress both show where those particular plugins keep their configuration.

