Nearly every plugin stores data in post meta: SEO titles, WooCommerce prices, page builder layouts, custom fields. Understanding how it’s stored helps you debug plugins, speed up slow sites and clean up a bloated database.

The table

Post meta lives in wp_postmeta (the prefix may differ):

Column Meaning
meta_id Primary key
post_id The post, page or custom post type it belongs to
meta_key The field name, e.g. _yoast_wpseo_title, _price
meta_value The value, stored as LONGTEXT

Keys starting with _ are “protected” — hidden from the Custom Fields box in the editor.

There are matching tables for other objects: wp_usermeta, wp_termmeta and wp_commentmeta.

Everything is text

meta_value is a text column. Numbers are stored as strings, and arrays or objects are serialized by PHP:

a:2:{s:5:"width";i:800;s:6:"height";i:600;}

Consequences:

  • Sorting numbers needs 'orderby' => 'meta_value_num' or 'type' => 'NUMERIC' in meta queries.
  • You can’t reliably search inside serialized values with SQL.
  • Never edit serialized data with a plain search-and-replace — changing a string’s length breaks it. Use wp search-replace, which handles serialization.

How WordPress loads meta

When you call get_post_meta() for a post, WordPress loads all meta for that post in one query and caches it for the rest of the request. In loops, WP_Query primes the meta cache for every post at once (update_post_meta_cache), so you don’t get one query per post.

Without a persistent object cache, that work repeats on every request. Adding Redis (with an object-cache plugin) keeps meta in memory between requests — a big win for WooCommerce and meta-heavy sites.

Inspecting meta

With WP-CLI:

wp post meta list 123
wp post meta get 123 _price
wp post meta update 123 _price 19.99

With SQL, find which plugins store the most meta:

SELECT meta_key, COUNT(*) AS rows_count
FROM wp_postmeta
GROUP BY meta_key
ORDER BY rows_count DESC
LIMIT 20;

Cleaning up

Deleting a plugin usually leaves its meta behind. Over years, wp_postmeta can grow to millions of rows.

Back up first: wp db export backup.sql.

Orphaned meta (rows whose post no longer exists):

DELETE pm FROM wp_postmeta pm
LEFT JOIN wp_posts p ON p.ID = pm.post_id
WHERE p.ID IS NULL;

Meta for old revisions is removed when revisions are deleted — limit revisions in wp-config.php:

define( 'WP_POST_REVISIONS', 10 );

Leftover keys from a removed plugin (check the key names first!):

wp db query "DELETE FROM wp_postmeta WHERE meta_key LIKE '\_oldplugin\_%';"

Performance notes

  • meta_key is indexed (first 191 characters); meta_value is not. Queries filtering on values scan many rows.
  • For data you filter constantly (e.g. thousands of events by date), a custom table or taxonomy is faster.
  • Give MySQL/MariaDB enough memory (innodb_buffer_pool_size) to keep wp_postmeta in RAM. On a VPS you control this setting — see Linux server performance.

Summary

Post meta is a flexible key/value store attached to posts. Know that values are text (and sometimes serialized), use WP-CLI for safe edits, add an object cache on busy sites, and clean up orphaned rows periodically. For the coding side — registering meta, meta boxes and queries — see WordPress custom post meta.

  • WordPress
  • Database
  • Development