WordPress stores user-specific data in the wp_usermeta table using the same key-value structure as post meta. There are built-in meta keys for display name, description, and social URLs, but the system is fully extensible: you can add any meta key you like with add_user_meta() or update_user_meta(), and expose it as a field in the user profile editor using the show_user_profile and edit_user_profile action hooks. Custom user meta is useful for: storing user preferences (preferred language, notification settings), extending the profile with business-specific fields (department, employee ID, phone number), building author profile pages with richer data, and tracking user-specific state (onboarding progress, last-seen post ID). The save logic uses personal_options_update and edit_user_profile_update hooks — both fire when the user profile form is saved, covering both the user editing their own profile and an administrator editing any profile.
Problem: You need to add custom fields to the WordPress user profile — for example a phone number, job title, or department — and save the values to wp_usermeta securely.
Solution: Display custom fields via show_user_profile / edit_user_profile, save with personal_options_update / edit_user_profile_update. Read and write meta with get_user_meta() and update_user_meta().
<?php
// ── Display custom fields on the profile form ────────────────────────────
add_action( 'show_user_profile', 'show_custom_user_fields' );
add_action( 'edit_user_profile', 'show_custom_user_fields' );
function show_custom_user_fields( $user ) {
$phone = esc_attr( get_user_meta( $user->ID, 'phone_number', true ) );
$dept = esc_attr( get_user_meta( $user->ID, 'department', true ) );
?>
<h3><?php esc_html_e( 'Additional Information', 'textdomain' ); ?></h3>
<table class="form-table">
<tr>
<th><label for="phone_number"><?php esc_html_e( 'Phone', 'textdomain' ); ?></label></th>
<td>
<input type="tel" name="phone_number" id="phone_number"
value="<?php echo $phone; ?>" class="regular-text">
</td>
</tr>
<tr>
<th><label for="department"><?php esc_html_e( 'Department', 'textdomain' ); ?></label></th>
<td>
<input type="text" name="department" id="department"
value="<?php echo $dept; ?>" class="regular-text">
</td>
</tr>
</table>
<?php
}
// ── Save custom fields ────────────────────────────────────────────────────
add_action( 'personal_options_update', 'save_custom_user_fields' );
add_action( 'edit_user_profile_update', 'save_custom_user_fields' );
function save_custom_user_fields( $user_id ) {
// Verify the current user can edit this profile
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return false;
}
update_user_meta( $user_id, 'phone_number',
sanitize_text_field( $_POST['phone_number'] ?? '' ) );
update_user_meta( $user_id, 'department',
sanitize_text_field( $_POST['department'] ?? '' ) );
}
// ── Read anywhere ────────────────────────────────────────────────────────
$phone = get_user_meta( get_current_user_id(), 'phone_number', true );
// Get all meta for a user (returns array of arrays)
$all_meta = get_user_meta( $user_id );
// Query users by meta value
$editors_in_sales = get_users( [
'role' => 'editor',
'meta_key' => 'department',
'meta_value' => 'Sales',
] );
NOTE: update_user_meta() creates the meta key if it does not exist and updates it if it does — there is no need to call add_user_meta() first unless you specifically want multiple values for the same key (add_user_meta() with $unique = false). Always validate with current_user_can( 'edit_user', $user_id ) before saving — without this check, any logged-in user who can reach the profile save endpoint could overwrite another user's meta.