WordPress fires a predictable set of action hooks around user account lifecycle events: user_register fires immediately after a new account is created and receives the new user ID; profile_update fires after an existing user’s profile is saved; delete_user fires before a user is deleted. These hooks are the foundation for any feature that extends the user management system — sending a custom welcome email with an onboarding checklist, assigning a default role based on how registration occurred, saving additional fields from a custom registration form, logging account creation to an audit trail, or provisioning a record in an external CRM. Combined with the registration_errors filter (which lets you validate extra fields and block registration with a custom error message), these hooks give you complete control over the registration and update lifecycle.
Problem: Your site's registration form has an extra "Company" field. You need to validate it server-side (required, max 100 chars), save it as user meta on registration, and send a customised welcome email that includes the company name.
Solution: Validate with registration_errors, save with user_register, and send a custom email in the same user_register callback after calling wp_new_user_notification() or replacing it entirely.
<?php
// ── 1. Add field to the registration form ─────────────────────────────
add_action( 'register_form', function () {
$company = isset( $_POST['company'] ) ? sanitize_text_field( $_POST['company'] ) : '';
?>
<p>
<label for="company"><?php esc_html_e( 'Company', 'textdomain' ); ?> <span class="required">*</span></label>
<input type="text" name="company" id="company"
class="input" value="<?php echo esc_attr( $company ); ?>" maxlength="100">
</p>
<?php
} );
// ── 2. Validate the field ─────────────────────────────────────────────
add_filter( 'registration_errors', function ( WP_Error $errors, $sanitized_user_login, $user_email ) {
$company = trim( sanitize_text_field( $_POST['company'] ?? '' ) );
if ( empty( $company ) ) {
$errors->add( 'company_required', __( '<strong>Error:</strong> Company name is required.', 'textdomain' ) );
} elseif ( mb_strlen( $company ) > 100 ) {
$errors->add( 'company_too_long', __( '<strong>Error:</strong> Company name must be 100 characters or fewer.', 'textdomain' ) );
}
return $errors;
}, 10, 3 );
// ── 3. Save field and send custom welcome email on registration ────────
add_action( 'user_register', function ( int $user_id ) {
// Save company as user meta
$company = sanitize_text_field( $_POST['company'] ?? '' );
if ( $company ) {
update_user_meta( $user_id, 'company', $company );
}
// Send custom welcome email
$user = get_userdata( $user_id );
$subject = sprintf( __( 'Welcome to %s, %s!', 'textdomain' ), get_bloginfo( 'name' ), $user->display_name );
$message = sprintf(
__( "Hi %s,
Your account for %s has been created.
Login: %s
Welcome aboard!", 'textdomain' ),
$user->display_name,
$company ?: __( 'your company', 'textdomain' ),
wp_login_url()
);
wp_mail( $user->user_email, $subject, $message );
}, 10 );
// ── 4. Hook profile updates ────────────────────────────────────────────
add_action( 'profile_update', function ( int $user_id, WP_User $old_user_data ) {
// Log role changes
$new_user = get_userdata( $user_id );
if ( $new_user->roles !== $old_user_data->roles ) {
error_log( "User $user_id role changed from " .
implode( ',', $old_user_data->roles ) . ' to ' .
implode( ',', $new_user->roles ) );
}
}, 10, 2 );
NOTE: $_POST data is only available in the user_register hook when registration happens through the standard wp-login.php?action=register form. If user accounts are created programmatically (e.g. via wp_insert_user() in an import script or REST API call), $_POST will be empty. For programmatic creation, pass the extra data directly as a parameter or use the function's return value (the new user ID) to call update_user_meta() immediately after creation.