To wordpress wpdb insert id — grab the auto-increment value of the row you just created — read $wpdb->insert_id immediately after the insert call. It’s populated after every INSERT the current connection runs, whether via $wpdb->insert() or a raw $wpdb->query('INSERT ...'). This guide covers the common case, the $wpdb->insert() vs raw-query distinction, bulk-insert semantics, and the “check return first, then read the ID” safety rule.
Last verified: 2026-04-23 on WordPress 6.5 and PHP 8.3. Originally published 2023-05-22, rewritten and updated 2026-04-23.
TL;DR
global $wpdb;
$ok = $wpdb->insert(
$wpdb->prefix . 'my_table',
array(
'name' => 'John',
'email' => '[email protected]',
)
);
if ( $ok === false ) {
// handle insert failure — do NOT read insert_id
}
$last_id = $wpdb->insert_id;
The idiomatic call
$wpdb->insert(
$wpdb->prefix . 'my_table',
array(
'name' => 'John',
'email' => '[email protected]',
)
);
$last_id = $wpdb->insert_id;
$wpdb->insert() runs a parameterized INSERT against the named table and returns the number of rows inserted on success (1) or false on failure. After the call, the insert_id property holds MySQL’s LAST_INSERT_ID() for this connection — the auto-increment value of the new row.
No extra round trip, no second SELECT — MySQL returns the value as part of the INSERT result packet, and $wpdb exposes it on the property.
Always check the return value first
$ok = $wpdb->insert( $wpdb->prefix . 'my_table', array(
'name' => $name,
'email' => $email,
) );
if ( $ok === false ) {
return new WP_Error( 'db_insert_failed', $wpdb->last_error );
}
$last_id = $wpdb->insert_id;
If the insert fails — unique-constraint violation, dropped connection, NOT NULL column missing — $wpdb->insert_id still holds whatever value the previous successful insert left there. Reading it without checking the return value can make the code act on a stale ID. The $wpdb->last_error property is the MySQL error string you’d want to log.

Using $wpdb->query() directly
$sql = $wpdb->prepare(
"INSERT INTO {$wpdb->prefix}my_table (name, email, created_at)
VALUES (%s, %s, NOW())",
$name,
$email
);
$ok = $wpdb->query( $sql );
if ( $ok === false ) {
return new WP_Error( 'db_insert_failed', $wpdb->last_error );
}
$last_id = $wpdb->insert_id;
The same insert_id property works when you’ve run the INSERT via $wpdb->query() (for expressions $wpdb->insert() can’t handle — NOW(), UUID(), subqueries). Reach for $wpdb->insert() when the values are simple; for SQL expressions in column values, fall back to query() + prepare().
Bulk inserts
$wpdb->query(
"INSERT INTO {$wpdb->prefix}my_table (name, email) VALUES
('John', '[email protected]'),
('Jane', '[email protected]'),
('Mike', '[email protected]')"
);
$first_id = $wpdb->insert_id; // e.g. 42
$rows = $wpdb->rows_affected; // 3
// Derived IDs: 42, 43, 44
$all_ids = range( $first_id, $first_id + $rows - 1 );
MySQL returns the ID of the first row in a multi-row INSERT. The other IDs are contiguous — assuming no other inserts from a concurrent connection interleaved — and you can derive them from $wpdb->rows_affected. Don’t run N separate single-row inserts just to collect N IDs; that’s N round trips for no reason.
Caveat: if your table has innodb_autoinc_lock_mode = 2 (the default on MySQL 8.0+ in some deployments), the IDs may not be strictly contiguous across concurrent inserts. For absolute certainty, wrap the bulk insert in a transaction and re-SELECT the rows by a batch identifier.
Frequently asked questions
Read $wpdb->insert_id right after $wpdb->insert(...) returns. The property holds the auto-increment value of the last INSERT the current connection performed — same semantics as MySQL’s LAST_INSERT_ID(). No extra query needed.
mysql_insert_id() the same thing? It was, back in the mysql_* PHP extension days. That extension was removed in PHP 7, so mysql_insert_id() no longer exists. WordPress’s $wpdb->insert_id is the modern replacement — it wraps mysqli_insert_id() internally and gives you a WordPress-idiomatic API.
$wpdb->insert_id after $wpdb->query()? Yes — it’s populated after any INSERT the connection runs, whether via $wpdb->insert(), $wpdb->query('INSERT ...'), or a prepared statement you execute manually. The rule: the property reflects the most recent INSERT, so read it immediately; a later non-INSERT query (SELECT, UPDATE) doesn’t overwrite it, but the next INSERT will.
MySQL returns the ID of the first row in a multi-row INSERT. If you inserted 10 rows with auto-incrementing IDs starting at 42, $wpdb->insert_id is 42, and the other nine IDs are 43–51 (assuming no gaps). Don’t call ten separate single-row inserts to “get each ID” — that’s ten round trips for no reason. The first ID + row count is enough to derive the rest.
No. If $wpdb->insert() fails (false return), $wpdb->insert_id holds whatever value the previous successful insert left there. Always check the return value before trusting the ID: if ($wpdb->insert(...) === false) { return new WP_Error(...); }. Otherwise you might end up acting on a stale ID.
Related guides
- How to Prepare a %LIKE% SQL Statement in WordPress — the companion
$wpdb->preparepattern for reads. - How to Search Users by Multiple Fields in WordPress — built-in class for searches instead of raw
$wpdb. - How to Order Posts by Meta Value in WordPress — another
WP_Queryvs raw SQL decision. - How to Retrieve the Last Inserted ID in Laravel — the Laravel-Eloquent equivalent of this pattern.
References
WordPress developer reference for $wpdb->insert and $wpdb->insert_id: developer.wordpress.org/reference/classes/wpdb/insert.