How to Create a Custom Login Page in WordPress

The default WordPress login page at wp-login.php works fine, but its generic branding sticks out on client projects. WordPress provides hooks to reskin it without touching any core files.

Problem: How do you rebrand the WordPress login page to match your client's site without editing core files?

Solution: Use the login_enqueue_scripts, login_headerurl, and login_headertext hooks to replace the logo, its link, and the page title, then enqueue a custom stylesheet.

The hooks below replace the logo, its link and title, and load a custom stylesheet on the login page:

<?php
// Replace the WordPress logo with your own
add_action( 'login_enqueue_scripts', 'custom_login_logo' );

function custom_login_logo() {
    $logo_url = get_stylesheet_directory_uri() . '/images/logo.png';
    ?>
    <style>
        #login h1 a {
            background-image: url( <?php echo esc_url( $logo_url ); ?> );
            background-size: contain;
            width: 200px;
            height: 60px;
        }
    </style>
    <?php
}

// Make the logo link point to the site, not wordpress.org
add_filter( 'login_headerurl', function() {
    return home_url( '/' );
} );

// Update the logo title/alt text
add_filter( 'login_headertext', function() {
    return get_bloginfo( 'name' );
} );

// Load a custom stylesheet on the login page
add_action( 'login_enqueue_scripts', 'enqueue_custom_login_styles' );

function enqueue_custom_login_styles() {
    wp_enqueue_style(
        'custom-login',
        get_stylesheet_directory_uri() . '/css/login.css',
        [],
        wp_get_theme()->get( 'Version' )
    );
}

To redirect users to a custom page after login based on their role:

add_filter( 'login_redirect', 'custom_login_redirect', 10, 3 );

function custom_login_redirect( $redirect_to, $request, $user ) {
    if ( $user instanceof WP_User ) {
        if ( $user->has_cap( 'manage_options' ) ) {
            return admin_url();
        }
        return home_url( '/dashboard/' );
    }
    return $redirect_to;
}

NOTE: The login_headertext filter was added in WordPress 5.2. For older installs, use the deprecated login_headertitle filter. Always add logo images via CSS — not via <img> tags injected with JavaScript.