Users API
WP_User, get_user_by(), wp_insert_user() and user meta: reading and writing WordPress users from PHP.
get_user_by( string $field, int|string $value ): WP_User|falseA WordPress user is one row in wp_users plus any number of rows in wp_usermeta. The Users API wraps both in the WP_User class so you never write that join by hand, and so that hooks fire when a user changes.
Reading
get_user_by( $field, $value ) accepts 'id', 'ID', 'slug', 'email' or 'login' and returns a WP_User or false. get_userdata( $user_id ) is shorthand for get_user_by( 'id', $user_id ) — the same call with the same return type. wp_get_current_user() gives the user for the current request, but it always returns an object: when nobody is logged in you get a WP_User whose ID is 0, so branch on is_user_logged_in(), not on the return value.
Writing
wp_insert_user( $userdata ) returns an int ID or a WP_Error. Pass an ID key and it updates instead of creating. wp_create_user( $username, $password, $email ) is shorthand for the same function with just those three keys; wp_update_user() is the same call again, requiring ID, and it hashes a plain-text user_pass for you.
Meta
Anything not a column in wp_users is meta: get_user_meta( $user_id, $key, $single ), update_user_meta( $user_id, $key, $value ). The meta_input array on wp_insert_user() writes meta in the same call. WP_User falls through to meta for any property it does not recognise, so a typo like $user->frist_name returns an empty string rather than a warning.
Example
<?php
/**
* Create a customer account and store the CRM id against it.
*
* @return WP_User|WP_Error
*/
function acme_create_customer( string $email, string $name, string $crm_id ) {
if ( email_exists( $email ) ) {
return new WP_Error( 'acme_exists', 'That email already has an account.' );
}
// For a NEW user, user_pass is plain text and wp_insert_user() hashes it.
$user_id = wp_insert_user( array(
'user_login' => sanitize_user( current( explode( '@', $email ) ), true ),
'user_email' => $email,
'user_pass' => wp_generate_password( 24 ),
'display_name' => $name,
'role' => 'customer', // the role must already exist
'meta_input' => array( // written to wp_usermeta in the same call
'acme_crm_id' => $crm_id,
),
) );
// wp_insert_user() returns int|WP_Error, never false.
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
// Shorthand for get_user_by( 'id', $user_id ) — identical result.
return get_userdata( $user_id );
}When to use it
- Provision accounts from an external CRM or billing system on webhook.
- Store per-user preferences that do not belong in wp_options.
- Build a front-end profile editor without touching wp_users directly.
- Look up a user by email before deciding to create or update them.
More examples
Reading the current user safely
Shows the fall-through from column to meta on WP_User, and the shorthand/longhand pair for a single meta value. Reach for the explicit get_user_meta() call in code others will read: the magic property gives no clue whether the value is a column or meta, and returns an empty string for keys that do not exist.
$user = wp_get_current_user();
// wp_get_current_user() always returns a WP_User object. A logged-out
// visitor gets one with ID 0, so this test is the reliable one.
if ( ! is_user_logged_in() ) {
return;
}
// Columns of wp_users come straight off the object.
$email = $user->user_email;
$login = $user->user_login;
// So does meta, via the same magic property — these two lines are equal:
$region = $user->acme_region;
$region = get_user_meta( $user->ID, 'acme_region', true ); // $single = true
// $single = false returns an array of every stored value for that key.
$badges = get_user_meta( $user->ID, 'acme_badge', false );Updating an existing user
The normal path for editing a user from a front-end profile form or a sync job. Use it rather than a direct wpdb UPDATE so that profile_update and the password-change hooks fire, and so caches are invalidated.
// wp_update_user() takes the same keys as wp_insert_user() but REQUIRES an ID.
// Unlike wp_insert_user(), it hashes a plain-text user_pass for you.
$result = wp_update_user( array(
'ID' => $user_id,
'user_email' => '[email protected]',
'description' => 'Handles EMEA accounts.',
'user_pass' => $plain_text_password, // omit the key to leave it unchanged
) );
if ( is_wp_error( $result ) ) {
error_log( 'acme: ' . $result->get_error_message() );
return;
}
// Meta is not part of the update payload unless you pass meta_input.
update_user_meta( $user_id, 'acme_last_synced', gmdate( 'Y-m-d H:i:s' ) );Deleting a user and reassigning their posts
Account closure and GDPR erasure flows. The require_once matters: wp_delete_user() is an admin-only include, and calling it from a REST route or a WP-Cron job without loading the file is a fatal error.
// wp_delete_user() lives in wp-admin, so load it on the front end or in cron.
if ( ! function_exists( 'wp_delete_user' ) ) {
require_once ABSPATH . 'wp-admin/includes/user.php';
}
// Second argument reassigns the user's posts and links to another user ID.
// Omit it and the posts are deleted with the user.
wp_delete_user( $user_id, $archive_user_id );
Discussion