WordPress uses a MySQL (or MariaDB) database. By default, it consists of 12 core tables. The 12 core tables are:
wp_commentmeta: Stores extra information (metadata) about comments.wp_comments: Contains all comments on posts and pages.wp_links: Stores information related to the Links feature (often deprecated but still included for backward compatibility).wp_options: Stores general site settings, configuration options, and temporary data (transients) for themes and plugins.(Site URL, Admin email, active plugins, and theme configurations).wp_postmeta: Stores metadata for posts, pages, and custom post types (e.g., custom fields).wp_posts: The core table that stores the content itself, including posts, pages, and navigation menu items.wp_terms: Stores categories, tags, and other taxonomies for posts and links.wp_termmeta: Stores metadata for terms.wp_term_relationships: Manages the associations between items in thewp_poststable and terms in thewp_termstable.wp_term_taxonomy: Defines the taxonomy (category, tag, etc.) for entries in thewp_termstable.wp_usermeta: Stores metadata and extra information about users.wp_users: Contains the list of registered users on the website
- The
wpdbClass is the “database driver.” It handles the actual connection and prevents security issues like SQL Injection.
global $wpdb;
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}posts WHERE post_status = 'publish'" );
- High-Level API Functions: Most developers use built-in functions that handle the SQL for them:
get_post($id): Grabs a row fromwp_posts.update_option('name', 'value'): Changes a row inwp_options.get_user_meta($user_id, 'key'): Grabs data fromwp_usermeta.
WP_Query is a PHP class that queries the WordPress database and returns posts based on rules you define.
$args = [
'post_type' => 'post',
'posts_per_page' => 5 // -1 for all posts
];
$query = new WP_Query( $args );
wp_postmeta table
| Column | Data Type | Key | Description |
|---|---|---|---|
meta_id | bigint(20) unsigned | PRI | The unique identifier for each metadata row (Auto-increment). |
post_id | bigint(20) unsigned | MUL | The ID of the post this metadata belongs to (Foreign key to wp_posts.ID). |
meta_key | varchar(255) | MUL | The name or “key” of the metadata (e.g., _wp_page_template). |
meta_value | longtext | The actual value of the metadata. Often stores serialized arrays. |
- Hidden vs. Visible Meta
- Public Meta: Keys that do not start with an underscore (e.g.,
my_custom_field) are usually visible in the WordPress admin dashboard under “Custom Fields.” - Protected Meta: Keys starting with an underscore (e.g.,
_edit_lastor_thumbnail_id) are hidden from the standard UI and are intended for internal plugin/theme use.
- Public Meta: Keys that do not start with an underscore (e.g.,
The meta_value column is a longtext type, allowing it to store massive amounts of data.
Note: Because
meta_valueis often serialized or contains long text, searching through it using SQLLIKEqueries can be very slow on large databases because it cannot be efficiently indexed.
post id + meta keycannot be made composite key, since same can appear mulitple times.
// Adding multiple features to the same post ID
add_post_meta(500, 'feature', 'Pool');
add_post_meta(500, 'feature', 'Garage');
// Returns an array: ['Pool', 'Garage']
$features = get_post_meta(500, 'feature', false);
// Returns only the first string: 'Pool'
$price = get_post_meta(500, '_price', true);
# You can allow only one value:
add_post_meta( $post_id, 'price', '19.99', true );
# If it doesn’t exist → it creates it
update_post_meta( $post_id, 'price', '24.99' );
meta_query and tax_query are arguments passed to WP_Query that let you filter posts by:
- meta_query → post meta (custom fields)
- tax_query → taxonomies (categories, tags, custom taxonomies)
$args = [
'post_type' => 'post',
'tax_query' => [
[
'taxonomy' => 'category',
'terms' => 'products'
]
],
'meta_query' => [
[
'key' => 'price',
'value' => 100,
'compare' => '<',
'type' => 'NUMERIC'
]
]
];
$query = new WP_Query( $args );
$postis a global PHP variable in WordPress.- It represents the current post object being processed in “The Loop.”
- Type:
WP_Postobject (class representing a single post in the database).
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post(); // Sets the global $post to the current post
?>
<h2><?php the_title(); ?></h2>
<div><?php the_content(); ?></div>
<?php
}
}
wp_reset_postdata();
// After a custom query loop, this restores the global $post to what it was before the loop (usually the main query).