Skip to main content

Database, SQL, PDO, and WordPress wpdb

WordPress Data API Preference

NeedPrefer
Query postsWP_Query, get_posts()
Post fieldsget_post(), template tags
Post metaget_post_meta(), update_post_meta()
User metaget_user_meta(), update_user_meta()
Term metaget_term_meta(), update_term_meta()
Site settingsget_option(), update_option()
Temporary cacheget_transient(), set_transient()
Custom SQL$wpdb

WP_Query and get_posts

$query = new WP_Query([
'post_type' => 'book',
'post_status' => 'publish',
'posts_per_page' => 10,
]);
$ids = get_posts([
'post_type' => 'book',
'fields' => 'ids',
'posts_per_page' => 20,
]);

Use wp_reset_postdata() after secondary loops.

Meta and Options

$value = get_post_meta($post_id, '_book_isbn', true);
update_post_meta($post_id, '_book_isbn', $isbn);
delete_post_meta($post_id, '_book_isbn');
$settings = get_option('myplugin_settings', []);
update_option('myplugin_settings', $settings, false);
delete_option('myplugin_settings');

Avoid autoloading large option data.

Transients

$items = get_transient('myplugin_items');

if (false === $items) {
$items = myplugin_fetch_items();
set_transient('myplugin_items', $items, 10 * MINUTE_IN_SECONDS);
}

wpdb Basics

global $wpdb;

$table = $wpdb->prefix . 'myplugin_logs';

Prepared Select

$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$table} WHERE id = %d",
$id
),
ARRAY_A
);

get_var, get_row, get_results

$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts}");
$row = $wpdb->get_row($sql, ARRAY_A);
$rows = $wpdb->get_results($sql, ARRAY_A);

Insert, Update, Delete

$wpdb->insert(
$table,
['user_id' => $user_id, 'event_name' => $event_name],
['%d', '%s']
);
$wpdb->update(
$table,
['event_name' => $event_name],
['id' => $id],
['%s'],
['%d']
);
$wpdb->delete($table, ['id' => $id], ['%d']);

prepare Placeholders

PlaceholderType
%sString
%dInteger
%fFloat
%iIdentifier in supported WordPress versions

Custom Tables and dbDelta

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);

PDO Outside WordPress

$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

Inside WordPress, use $wpdb unless there is a strong reason not to.

Performance Notes

  • Request only needed fields.
  • Avoid slow meta queries on large sites.
  • Add indexes for custom tables based on real query patterns.
  • Cache expensive computed values.
  • Avoid running write queries on every page load.
  • Use Query Monitor during development.

Common Pitfalls

  • Using raw SQL when WP_Query is enough.
  • Forgetting $wpdb->prepare().
  • Using post meta for high-volume relational data.
  • Creating custom tables without schema versioning.
  • Storing large autoloaded options.
  • Forgetting multisite prefixes.