BuddyX

13 min read · 2,624 words

How Many Table In A Default WordPress Site

How Many Table In A Default WordPress

A stock WordPress install creates twelve database tables. That number holds steady across versions and hasn’t changed in years, which is worth knowing on its own, but the more useful question is usually what’s inside those twelve tables, how they relate to each other, and why that count balloons dramatically the moment you install WooCommerce, a caching plugin, or basically anything beyond a bare theme. Here’s the actual structure, table by table, plus what typically adds to it.

The Twelve Core Tables

wp_posts

The center of gravity for the entire database. Every post, page, attachment, revision, and custom post type registered by any plugin lives here as a row, distinguished by the post_type column. This single-table-for-everything design is deliberate, rather than a separate table for pages, a separate table for posts, and a separate table for every custom post type a plugin might register, WordPress uses one flexible schema and a type column, which is why third-party plugins can register entirely new content types without needing to modify the database schema at all.

wp_postmeta

The companion table to wp_posts, storing arbitrary key-value pairs attached to any post. This is where custom fields live, where SEO plugins store their meta title and description, where Advanced Custom Fields stores its field values, and where page builders store their layout data, usually as serialized PHP arrays. A single post can have dozens or hundreds of postmeta rows depending on how many plugins are touching it. This table is also the single most common performance bottleneck on large WordPress sites, because postmeta queries filtering by meta_key and meta_value against a large table without the right indexing strategy get slow fast.

wp_terms

Stores the actual names of taxonomy terms, the literal word “WordPress” if that’s a tag, or “Tutorials” if that’s a category. Just the term name and a slug, nothing about what taxonomy it belongs to or what it’s attached to; that relationship logic lives in the next two tables.

wp_term_taxonomy

Links each term to a specific taxonomy (category, post_tag, or a custom taxonomy registered by a plugin) and stores taxonomy-specific data like description and parent term for hierarchical taxonomies like categories. The separation between wp_terms and wp_term_taxonomy exists because the same term name can theoretically be used across different taxonomies without collision.

wp_term_relationships

The join table connecting individual posts to the taxonomy terms attached to them, this is literally what makes “this post is in the Tutorials category and tagged WordPress and Database” work, by storing rows linking a post’s ID to each relevant term_taxonomy_id.

wp_users

Account records: username, hashed password, email, registration date, and a display name. Notably lean compared to wp_usermeta, because almost everything beyond these core fields is stored as meta rather than as dedicated columns.

wp_usermeta

The same key-value pattern as postmeta, but for users. This is where a user’s role and capabilities are actually stored (as a serialized array under the wp_capabilities meta key), along with admin color scheme preference, dismissed admin notices, and any custom profile fields a plugin adds. If you’re ever debugging why a user’s role seems wrong despite what the admin UI shows, this table is where the actual source of truth lives.

wp_options

The catch-all for site-wide configuration: site URL, admin email, active plugins list, theme settings, permalink structure, and essentially any setting a plugin needs to persist that isn’t tied to a specific post or user. Options can be “autoloaded,” meaning WordPress pulls them into memory on every single page load regardless of whether that page actually needs them, which is why a bloated wp_options table full of unnecessarily autoloaded data is a well-known and common cause of slow WordPress sites, poorly coded plugins that stash large serialized arrays as autoloaded options are a frequent culprit.

wp_comments

Comment content, author name and email (for non-logged-in commenters), the associated post ID, and moderation status. Straightforward, though on a high-traffic site with an active comment section, this table can grow large enough to need its own indexing attention.

wp_commentmeta

Key-value metadata attached to comments, following the same pattern as postmeta and usermeta. Less commonly used by default WordPress functionality, but plugins like comment rating systems or spam-filtering tools (Akismet stores some of its data here) rely on it.

A holdover from very early WordPress versions, originally used for the built-in “blogroll” feature that let site owners maintain a curated list of external links. The Link Manager feature was removed from WordPress core’s default UI back in version 3.5 (2012), but the underlying table and its supporting functions remain in core for backward compatibility with sites that still use it via a plugin that restores the feature. On the overwhelming majority of modern WordPress sites, this table exists but sits completely empty and unused.

wp_termmeta

Added in WordPress 4.4 (2015), this rounds out the meta-table pattern by giving taxonomy terms the same key-value storage capability that posts, users, and comments already had. Before this table existed, plugins needing to attach custom data to a category or tag had to work around the limitation with awkward workarounds; wp_termmeta gave them a proper, standard place to store it.

The Pattern Worth Noticing

Look at the twelve tables as a group and a clear design pattern emerges: four core content-holding tables (posts, terms, users, comments) each paired with a meta table that lets any plugin attach arbitrary structured data without WordPress needing to know in advance what that data will be. This is the actual mechanism that makes WordPress’s plugin ecosystem work as smoothly as it does, a plugin author never needs core WordPress to modify its schema on their behalf; they just start writing rows into the appropriate meta table with their own custom meta_key names, and WordPress’s existing get_post_meta(), get_user_meta(), and similar functions handle reading it back out.

Table Prefix: Why It’s Not Always “wp_”

The wp_ prefix used throughout this article is WordPress’s default, set during installation, but it’s neither fixed nor required to be that literal string. Security guidance for years has recommended changing it to something less predictable during a fresh install, on the theory that SQL injection attacks targeting known table names are marginally harder to construct against a non-default prefix. This protection is fairly weak in practice against a competent attacker (the prefix is discoverable through other means in most real attack scenarios), but it costs nothing to set during initial installation and remains a reasonable low-effort hardening step. Changing the prefix after a site is already live is possible but involves careful search-and-replace across the database plus updating wp-config.php, and getting it wrong breaks the site, so it’s a change best made once, at install time, rather than retrofitted later.

Multisite installs multiply this further: each subsite in a WordPress multisite network gets its own set of these core tables with a numbered prefix (wp_2_posts, wp_3_posts, and so on for each subsite beyond the main one), while a smaller set of genuinely network-wide tables (wp_blogs, wp_site, wp_sitemeta, wp_users, and wp_usermeta, since user accounts are shared across the whole network) exist only once at the network level.

What Actually Adds Tables to a Real Site

Almost no production WordPress site runs with just these twelve tables, because almost no production site runs with zero plugins. Here’s what typically shows up:

WooCommerce adds a substantial number of its own tables, wp_woocommerce_order_items, wp_woocommerce_order_itemmeta, wp_woocommerce_sessions, wp_woocommerce_tax_rates, and several more, because order data, cart sessions, and tax configuration don’t map cleanly onto WordPress’s generic post/postmeta pattern at the scale and query pattern e-commerce needs. Recent WooCommerce versions have also moved toward storing orders in dedicated custom tables (via the High-Performance Order Storage feature) rather than as a custom post type, specifically because postmeta-based storage doesn’t scale well for high order volumes.

Caching plugins like W3 Total Cache or WP Rocket sometimes add their own tables for tracking cache state and statistics, though many caching plugins rely primarily on the filesystem or an external object cache (Redis, Memcached) rather than additional database tables.

Form plugins such as Gravity Forms add dedicated tables for form entries, form fields, and entry meta, again because generic postmeta storage doesn’t perform well for the query patterns a form submission dashboard needs (filtering and sorting large volumes of structured entry data).

SEO plugins like Yoast SEO and Rank Math typically add a handful of tables for tracking internal linking suggestions, redirects, and indexing status, supplementing what they also store in postmeta for per-post SEO data.

Security plugins like Wordfence maintain their own tables for logging login attempts, firewall events, and scan results, since this data needs fast writes and doesn’t belong mixed into WordPress’s core content tables.

Membership and LMS plugins (LearnDash, MemberPress, and similar) frequently add tables for tracking course progress, enrollment status, and access rules, for the same reason: this data has a different shape and query pattern than a generic postmeta key-value store handles efficiently at scale.

It’s not unusual for a moderately plugin-heavy production WordPress site to have thirty, forty, or more tables once everything is accounted for, even though the WordPress core installation itself never grows beyond its original twelve. A large multi-vendor marketplace or a busy membership community site with several of these plugin categories stacked together can easily clear fifty or sixty tables total, and none of that is inherently a problem as long as each addition is earning its keep with real functionality rather than sitting there as leftover cruft from a plugin nobody actively uses anymore.

How the Meta Tables Actually Perform at Scale

The elegance of the meta-table pattern (postmeta, usermeta, termmeta, commentmeta all sharing the same key-value structure) comes with a real cost once a site grows past a certain size, and it’s worth understanding why. Each of these tables is fundamentally an Entity-Attribute-Value structure: rather than dedicated columns for each piece of data, everything is stored as rows of (object ID, meta key, meta value). This is flexible, any plugin can add new “columns” of data without an actual schema migration, but it means a query looking for “all posts where a specific custom field equals a specific value” has to filter a potentially enormous table by two string columns (meta_key and meta_value) rather than a single indexed numeric column, which is inherently slower than a purpose-built column would be.

On a site with a few hundred posts, this never becomes noticeable. On a site with tens of thousands of posts and several plugins each adding multiple postmeta rows per post, the postmeta table can balloon into millions of rows, and meta-value queries that were instant at a smaller scale start showing up in slow query logs. This is exactly why WooCommerce moved order data out of postmeta into dedicated tables with real columns and proper indexes as order volume grew across the ecosystem, the general-purpose meta pattern that works beautifully for content simply doesn’t hold up under transactional, high-volume query patterns.

Inspecting These Tables Yourself

If you want to look at your own site’s actual table list rather than taking this article’s word for it, phpMyAdmin (available through most hosting control panels) or Adminer gives you a direct, visual view of every table currently in your database, including everything plugins have added beyond the core twelve. For a faster, script-friendly check, WP-CLI’s wp db tables command lists every table with the current site’s prefix applied, and wp db size --tables shows the size of each one, which is a genuinely useful diagnostic when you’re trying to figure out what’s actually consuming disk space or slowing down backups on a site that’s grown large over time.

A quick, safe way to see which tables are unexpectedly large: wp db query "SELECT table_name, round(((data_length + index_length) / 1024 / 1024), 2) AS size_mb FROM information_schema.TABLES WHERE table_schema = DATABASE() ORDER BY size_mb DESC LIMIT 15;" surfaces the fifteen largest tables in your database by size, which is often the fastest way to spot a runaway postmeta table, a bloated sessions table left over from an abandoned plugin, or a logging table that was never set up to prune old rows.

Common Questions

Why does WordPress use so many separate tables instead of one big table with lots of columns? Normalization, splitting related data into separate tables connected by IDs rather than duplicating data or cramming unrelated concerns into one giant table, is standard relational database design practice, and it’s what lets WordPress efficiently query “give me this post’s comments” or “give me this user’s posts” without scanning irrelevant data. It also means adding a new kind of relationship (like taxonomy support for a new content type) doesn’t require restructuring the whole database, just adding new rows to existing relationship tables.

Is it safe to delete the unused wp_links table? Not recommended even though it’s typically empty, since WordPress core still references it in a handful of legacy code paths, and a small number of older themes and plugins may still expect it to exist. The storage cost of an empty table is negligible, so there’s no real upside to removing it, and a small but real risk if something unexpected still depends on its presence.

Do custom post types create new tables? No, and this is a common misconception. A custom post type registered through register_post_type() stores its entries in the same wp_posts table as regular posts and pages, distinguished only by the post_type column’s value. This is exactly the flexible design mentioned earlier: no schema changes needed, just a new value in an existing column. Plugins only add genuinely new tables when the query patterns or data structure genuinely don’t fit the generic post/postmeta model well, as with WooCommerce orders or Gravity Forms entries.

What happens to these tables when I uninstall a plugin? This depends entirely on whether the plugin’s developer implemented proper uninstall cleanup. A well-built plugin includes an uninstall.php file (or hooks into the register_uninstall_hook() function) that drops its custom tables and removes its options and postmeta when you choose “Delete” rather than just “Deactivate” through the Plugins screen. Plenty of plugins skip this step, leaving orphaned tables and options behind indefinitely, which is a real, if usually minor, contributor to database bloat on sites that have cycled through many plugins over the years.

Does a larger number of tables slow down WordPress? Not directly, no. Database performance is governed far more by the size of individual tables, the queries run against them, and whether appropriate indexes exist, than by the raw count of tables in the schema. A site with sixty small, well-indexed tables will typically outperform a site with twelve tables where postmeta has grown to several million poorly queried rows. Table count is a rough proxy for plugin count, which is a weak indirect signal at best, not a direct performance metric worth optimizing on its own.

Why This Matters Beyond Trivia

Understanding this structure has practical value beyond satisfying curiosity. When you’re debugging a plugin conflict, knowing that a plugin’s custom data probably lives in postmeta (if it’s simple) or a dedicated custom table (if it’s high-volume or structured data) tells you where to look. When you’re troubleshooting a slow site, knowing that wp_options and wp_postmeta are the two tables most commonly responsible for performance problems on a bloated, plugin-heavy install gives you a starting point rather than guessing blindly. And when you’re evaluating whether a plugin is well-built, checking whether it adds sensible dedicated tables for high-volume structured data (a sign of a mature, scalability-conscious codebase) versus dumping everything into postmeta regardless of volume (a common shortcut in less carefully built plugins) is a genuinely useful signal.


Interesting Reads:

What Table Is The Admin Info Stored In WordPress

Why Does WordPress Use MySQL?

How To Convert A WordPress Site To A Static HTML Website

Reading
13 min · 2,624 words
Published
Aug 26, 2024
Shashank Dubey
BuddyX contributor

Writing about WordPress communities, BuddyPress, BuddyBoss, LMS plugins, and the business of paid communities.

Keep reading

More from the BuddyX blog

Browse all posts on community, WordPress, BuddyPress and the studio of plugins behind BuddyX.