WordPress User Registration Hooks: Validate Custom Fields, Save Meta, and Send Welcome Email

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.