RevealTheme logo
Back to Blog

WordPress Database Schema: What Actually Lives In Each Table

WordPress Database Schema: What Actually Lives In Each Table
The RevealTheme Team

By

··Updated May 27, 2026·5 min read

WordPress's database schema consists of 12 core tables plus tables added by plugins. The core tables handle: content (posts, pages, custom post types), users, terms (categories, tags, taxonomies), options (site configuration), and the relationships between these.

Understanding the structure isn't required for using WordPress, but it's necessary for: debugging database issues, designing custom queries, building integrations that work directly with the database, and migrating between systems.

The content tables

wp_posts: stores all "post-like" content. This isn't just blog posts; it's also pages, custom post types, attachments (uploaded files), revisions, and auto-saves. Each row is one piece of content with: ID, post_author, post_date, post_content, post_title, post_excerpt, post_status, post_type, etc.

The post_type column distinguishes the content types. Standard post types: 'post' (blog posts), 'page' (static pages), 'attachment' (media), 'revision' (post revisions), 'nav_menu_item' (menu items). Custom post types add their own values.

wp_postmeta: stores custom field data for posts. Each row is one custom field on one post: meta_id, post_id, meta_key, meta_value. The structure is generic; any data can be stored as a key-value pair attached to a post.

Performance consideration: wp_postmeta can grow very large on sites that use many custom fields. Indexing the meta_key column helps, but querying for posts based on meta values is inherently slow at scale. This is one reason ACF's "post object" relationship has performance limits.

The user tables

wp_users: stores user account data. ID, user_login, user_email, user_pass (hashed), user_registered, display_name. Standard authentication data.

wp_usermeta: stores extended user data. The pattern matches wp_postmeta: user_id, meta_key, meta_value. WordPress core uses this for user roles, capabilities, profile fields, and various preferences. Plugins extend it with their own meta keys.

The user role isn't stored in wp_users; it's in wp_usermeta as the wp_capabilities meta key. The capabilities are serialized data; reading them requires PHP's unserialize() or WordPress's get_user_meta() function.

The taxonomy tables

wp_terms: stores the term itself. term_id, name, slug, term_group. A term is just a label; "WordPress" might be a term used in both categories and tags.

wp_term_taxonomy: stores the taxonomy assignment for terms. term_taxonomy_id, term_id, taxonomy, description, parent, count. The taxonomy column distinguishes how the term is used: 'category', 'post_tag', or a custom taxonomy name.

wp_term_relationships: stores the assignments of terms to posts. object_id (the post), term_taxonomy_id, term_order. Many-to-many: a post can have multiple terms, a term can be assigned to multiple posts.

The three-table structure for taxonomies is more complex than seems necessary at first glance. The reason is that the same term name can be used across different taxonomies (a category and a tag can both be "WordPress"). The separation lets terms be shared while maintaining taxonomy-specific information.

The comments tables

wp_comments: stores comments on posts. comment_ID, comment_post_ID, comment_author, comment_author_email, comment_content, comment_approved, etc.

wp_commentmeta: stores comment metadata. comment_id, meta_key, meta_value. Used for spam tracking, anti-bot tokens, and plugin-specific data.

The options table

wp_options: stores site-wide configuration. option_id, option_name, option_value, autoload. Each option is a key-value pair.

The autoload column matters significantly. Options with autoload='yes' are loaded into memory on every WordPress page load. Too many autoloaded options or autoloaded options with large values slow down every page.

The wp_options table accumulates entries from plugins over years. Many plugins leave options behind after uninstall. The maintenance pattern: periodically clean up orphaned options.

The link tables

wp_links and wp_term_relationships handle the older blogroll functionality and the term-to-post linking. wp_links is mostly deprecated; modern WordPress doesn't use blogrolls.

The plugin tables

Plugins add their own tables for plugin-specific data. The naming convention is to prefix tables with the plugin name (wp_woocommerce_*, wp_wpforms_*, wp_yoast_*).

Quality plugins clean up their tables on uninstall. Less quality plugins leave the tables behind. Sites that have installed and uninstalled many plugins accumulate orphaned tables.

The audit: query SHOW TABLES FROM database_name to list all tables. Compare against the WordPress core list. Plugin tables that look unfamiliar might be from uninstalled plugins.

The relationships in practice

To get all posts with their categories and tags, you join wp_posts to wp_term_relationships (by post ID) to wp_term_taxonomy (by term_taxonomy_id) to wp_terms (by term_id), filtering wp_term_taxonomy by the desired taxonomy.

The query is more complex than it would be in a simpler schema, but the flexibility of the schema (handling many taxonomies and many content types in the same structure) is what enables WordPress's plugin ecosystem.

The performance implications

WordPress's "everything is a post" approach is operationally flexible but creates performance challenges at scale. A site with 50,000 posts of different types, each with extensive postmeta, can struggle with queries that filter or sort across these dimensions.

The standard optimization patterns: index the meta_key columns in wp_postmeta and wp_usermeta, archive old content if not needed for queries, denormalize critical data into custom tables for high-traffic queries.

For most sites, the standard schema is acceptable. The optimization concerns are real but specific to high-scale use cases.

The schema changes over WordPress versions

WordPress's schema has been remarkably stable since version 3. New columns get added occasionally (post_password, post_mime_type for attachments), but the core structure is unchanged for over a decade.

The stability is valuable. Plugins from 2015 still work on the 2026 schema. Custom code that interacted with the database directly years ago continues to work today.

The stability comes at a cost: the schema reflects 2003-era design decisions. The flexibility comes from generic key-value stores (postmeta, options, usermeta) that produce slow queries at scale.

The honest framing

You don't need to know WordPress's schema to operate a WordPress site. Most operations happen through WordPress's APIs, which abstract away the database.

You do need to know the schema if you're: debugging database issues that aren't visible through admin tools, building integrations that read or write directly to the database, migrating data between WordPress and another system, optimizing queries on sites with performance issues.

For those cases, the schema is documented in WordPress's Codex and developer documentation. The structure isn't intimidating once you understand the core patterns (content in posts/postmeta, users in users/usermeta, taxonomies across three tables). A few hours of study gives you operational understanding for most practical purposes.