WordPress Database API (WPDB)

WordPress database abstraction layer built on MySQLi.

Since: 0.71
Source: wp-includes/class-wpdb.php

Components

Component Description
class-wpdb.md Main database class – properties, methods, usage
hooks.md Database-related actions and filters

Architecture

Global Instance

WordPress instantiates a single global $wpdb object during bootstrap:

global $wpdb;

// Use the global instance
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->posts}" );

Connection Flow

wp-config.php (credentials)
    └── wp-settings.php
            └── require_wp_db()
                    └── new wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST )
                            ├── __construct()
                            │       ├── init credentials
                            │       └── db_connect()
                            ├── init_charset()
                            ├── set_charset()
                            ├── set_sql_mode()
                            └── select( DB_NAME )

Drop-In Support

Custom database handlers via wp-content/db.php:

// wp-content/db.php
class my_wpdb extends wpdb {
    // Custom implementation
}

$wpdb = new my_wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );

Table Prefixes

WordPress uses prefixes to namespace tables, enabling multiple installations in one database.

Prefix Properties

Property Description Example
$prefix Current blog prefix wp_ or wp_2_
$base_prefix Base prefix (no blog ID) wp_

Table Name Properties

Access prefixed table names directly:

global $wpdb;

// Per-site tables
$wpdb->posts;            // wp_posts
$wpdb->postmeta;         // wp_postmeta
$wpdb->comments;         // wp_comments
$wpdb->commentmeta;      // wp_commentmeta
$wpdb->terms;            // wp_terms
$wpdb->term_taxonomy;    // wp_term_taxonomy
$wpdb->term_relationships; // wp_term_relationships
$wpdb->termmeta;         // wp_termmeta
$wpdb->options;          // wp_options
$wpdb->links;            // wp_links

// Global tables (shared in multisite)
$wpdb->users;            // wp_users
$wpdb->usermeta;         // wp_usermeta

// Multisite-only global tables
$wpdb->blogs;            // wp_blogs
$wpdb->blogmeta;         // wp_blogmeta
$wpdb->site;             // wp_site
$wpdb->sitemeta;         // wp_sitemeta
$wpdb->signups;          // wp_signups
$wpdb->registration_log; // wp_registration_log

Using Table Names in Queries

Always use properties, never hardcode:

// ✅ Correct
$wpdb->query( "SELECT * FROM {$wpdb->posts} WHERE post_status = 'publish'" );

// ❌ Wrong - breaks with custom prefixes
$wpdb->query( "SELECT * FROM wp_posts WHERE post_status = 'publish'" );

Multisite Table Switching

// Get prefix for specific blog
$blog_prefix = $wpdb->get_blog_prefix( $blog_id );

// Switch blog context
$old_blog_id = $wpdb->set_blog_id( $new_blog_id );

// Returns to original after use
$wpdb->set_blog_id( $old_blog_id );

Prepared Statements

CRITICAL: Always use $wpdb->prepare() for user input to prevent SQL injection.

Placeholders

Placeholder Type Description
%d Integer Signed integer
%f Float Floating-point number
%s String String (auto-quoted)
%i Identifier Table/column names (since 6.2)

Basic Usage

// Single placeholder
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE ID = %d",
    $post_id
);

// Multiple placeholders
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s",
    $post_type,
    $status
);

// Array of values
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE ID = %d AND post_type = %s",
    array( $post_id, $post_type )
);

Identifier Placeholder (%i)

For dynamic table/column names (since WordPress 6.2):

$sql = $wpdb->prepare(
    "SELECT * FROM %i WHERE %i = %s",
    $table_name,
    $column_name,
    $value
);

Numbered Placeholders

$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE post_author = %1$d OR post_author = %1$d",
    $author_id
);

LIKE Queries

Use $wpdb->esc_like() before prepare():

$search = $wpdb->esc_like( $user_input );
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
    '%' . $search . '%'
);

Escaping Literal %

Double the percent sign:

$sql = $wpdb->prepare(
    "SELECT DATE_FORMAT(post_date, '%%Y-%%m') FROM {$wpdb->posts} WHERE ID = %d",
    $post_id
);

Query Methods Overview

Data Retrieval

Method Returns Use Case
get_var() Single value Counts, single field
get_row() Single row One record
get_col() Array of values One column from many rows
get_results() Array of rows Multiple records
query() Affected rows or bool Direct SQL execution

Data Modification

Method Operation Returns
insert() INSERT Rows inserted (1) or false
replace() REPLACE Rows affected or false
update() UPDATE Rows updated or false
delete() DELETE Rows deleted or false

After INSERT

$wpdb->insert( $wpdb->posts, array( 'post_title' => 'Hello' ) );
$new_id = $wpdb->insert_id; // Auto-increment ID

Output Formats

Methods like get_row() and get_results() accept an output format:

Constant Type Description
OBJECT stdClass Default. Object with column properties
OBJECT_K array Objects keyed by first column value
ARRAY_A array Associative array (column => value)
ARRAY_N array Numeric array (indexed)
// As object (default)
$row = $wpdb->get_row( $sql, OBJECT );
echo $row->post_title;

// As associative array
$row = $wpdb->get_row( $sql, ARRAY_A );
echo $row['post_title'];

// As numeric array
$row = $wpdb->get_row( $sql, ARRAY_N );
echo $row[0]; // First column

Error Handling

Check for Errors

$result = $wpdb->query( $sql );

if ( $result === false ) {
    // Query failed
    $error = $wpdb->last_error;
}

Debug Mode

// Show errors (typically in wp-config.php)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', true );

// Or programmatically
$wpdb->show_errors();
$wpdb->hide_errors();
$wpdb->suppress_errors( true );

Query Logging

// Enable in wp-config.php
define( 'SAVEQUERIES', true );

// Access logged queries
print_r( $wpdb->queries );
// Each entry: [ SQL, time, caller, start_time, custom_data ]

Properties Reference

Query State

Property Type Description
$last_query string Last executed SQL
$last_result array Results from last SELECT
$last_error string Last error message
$num_queries int Total queries this request
$num_rows int Rows returned by last SELECT
$rows_affected int Rows affected by last INSERT/UPDATE/DELETE
$insert_id int AUTO_INCREMENT ID from last INSERT

Connection

Property Type Description
$ready bool Connection ready for queries
$charset string Connection character set
$collate string Connection collation

Character Set & Collation

Automatic Handling

WordPress automatically handles character set conversion. The default is utf8mb4.

// Get charset/collation for CREATE TABLE
$charset_collate = $wpdb->get_charset_collate();
// Returns: "DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci"

Capability Checking

if ( $wpdb->has_cap( 'utf8mb4' ) ) {
    // Database supports utf8mb4
}

if ( $wpdb->has_cap( 'utf8mb4_520' ) ) {
    // Supports utf8mb4_unicode_520_ci
}

if ( $wpdb->has_cap( 'identifier_placeholders' ) ) {
    // Supports %i placeholder (6.2+)
}

Best Practices

1. Always Use Prepared Statements

// ✅ Safe
$wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d", $id );

// ❌ SQL Injection vulnerability
$wpdb->query( "SELECT * FROM {$wpdb->users} WHERE ID = $id" );

2. Use Table Properties

// ✅ Prefix-aware
"SELECT * FROM {$wpdb->posts}"

// ❌ Breaks with custom prefixes
"SELECT * FROM wp_posts"

3. Check Return Values

$inserted = $wpdb->insert( $wpdb->posts, $data );
if ( $inserted === false ) {
    // Handle error
    error_log( $wpdb->last_error );
}

4. Use Convenience Methods

// ✅ Preferred for simple operations
$wpdb->insert( $wpdb->postmeta, array(
    'post_id'    => $post_id,
    'meta_key'   => '_custom_field',
    'meta_value' => $value,
) );

// ❌ Unnecessary for simple inserts
$wpdb->query( $wpdb->prepare(
    "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES (%d, %s, %s)",
    $post_id, '_custom_field', $value
) );

5. Specify Formats for Type Safety

$wpdb->insert(
    $wpdb->postmeta,
    array(
        'post_id'    => $post_id,
        'meta_key'   => $key,
        'meta_value' => $value,
    ),
    array( '%d', '%s', '%s' ) // Explicit formats
);

Performance Considerations

Query Caching

WordPress object cache integrates with WPDB. Use higher-level APIs when possible:

// ✅ Uses cache
$post = get_post( $id );

// ❌ Bypasses cache
$post = $wpdb->get_row( $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $id
) );

Limit Results

// Use LIMIT to prevent loading huge datasets
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} LIMIT 100" );

Flush After Large Operations

$wpdb->flush(); // Clear cached results, free memory