You can customize the WordPress reset password page with a plugin, with login hooks, with WooCommerce hooks or with your own page. The first three take minutes. The fourth takes real code but gives you full control of the markup.
We tested every method in October 2026 on a WordPress 7.1.3 test site with WooCommerce 11.2 and Theme My Login 7.3.1. Each reset ran end to end, from the request form to a saved password, with the email captured so we could read it.
This guide shows the code we ran and explains where older snippets go wrong. It ends with a testing checklist and six short answers. If you add code, put it in a child theme or a small plugin so a theme update cannot erase it.
How the WordPress Reset Password Flow Works
The WordPress reset flow has three steps. A visitor asks for a reset on the lost password screen, and WordPress emails a one-time link. The link opens a form where the visitor saves a new password, and you can change each screen on its own. On a WooCommerce store, fingerprint and face login plugins let customers who forget passwords sign in without one.
The WordPress forgot password form lives at wp-login.php with the action set to lostpassword. The login_form_{$action} hook fires for each action that reaches wp-login.php, and its name includes the action, such as login_form_lostpassword or login_form_resetpass.

The email link carries a login name and a key. When we opened it, core stored them in a cookie and redirected to a clean wp-login.php?action=rp address.
That detail matters for Method 4. The first request with a key arrives as the resetpass action and never as rp.
WordPress fires a different hook for each screen:
- login_form_lostpassword: Runs on the request form, which makes it the place for a note or an extra field.
- login_form_resetpass: Runs when a link with a key arrives, and again when core’s own reset form posts.
- login_form_rp: Runs on the clean reset address after core has stored the key.
- login_enqueue_scripts: Runs on every wp-login.php screen. It suits CSS and scripts.
Method 1: Use Theme My Login for Themed Reset Pages
Theme My Login replaces the wp-login.php screens with pages that sit inside your theme. The reset pages share your header, footer and fonts. It needs no code beyond a few slug settings. It is the quickest route when you also want themed login and registration pages.
The plugin listing shows version 7.3.1, more than 50,000 active installations and a tested-up-to version of 7.1.3 as of October 2026. Its readme says the pages appear at /login, /register and /lostpassword by default. Follow these steps:
- Install and activate it: Go to Plugins > Add Plugin and search for Theme My Login. Install and activate it.
- Open the settings: Go to Theme My Login > General and scroll to Slugs. The Lost Password and Reset Password slugs set the page addresses.

- Visit the new page: Open /lostpassword/ on your site. The form now appears inside your theme.

We ran a full reset through these pages. The email linked to /resetpass/, and saving a new password landed on /login/ with the message “Your password has been reset.” Our test then confirmed that the stored password matched the new one.
Two trade-offs showed up. The buttons took on the plain default styling of the Twenty Twenty-Five theme. You may still need CSS.
The readme also lists paid add-ons. Notifications rewrites account emails, and Redirection controls where users land after login, logout and registration. We did not test those.
You can also place a form on any page you design. The readme documents a shortcode, [theme-my-login action="lostpassword"], that shows the lost password form inside the page. The readme adds that WordPress’s own login, logout and registration links point to the plugin’s pages once it is active.
Method 2: Customize the WordPress Reset Password Page With Login Hooks
You can customize the WordPress reset password page without a plugin by using the hooks that wp-login.php already fires. Add CSS, swap the logo link, change the labels and add a note. Each hook runs only on the login screens. Your front end stays untouched.
This keeps the default screens. The reset logic stays core code. The same hooks also let you customize WordPress login page screens such as the main login form.
The login_enqueue_scripts action is the documented place to load assets on login screens, and it handles styles as well as scripts. The core stylesheet there uses the handle login. Attach your CSS to it with wp_add_inline_style, and put login-logo.png next to the plugin file:
<?php
defined( 'ABSPATH' ) || exit;
/**
* Add CSS to wp-login.php. The 'login' handle is the core stylesheet for that screen.
*/
function maple_login_styles() {
$css = '
body.login { background: #fdf6ee; }
.login h1 a { width: 100%; height: 72px; background-size: contain; }
#login { width: 380px; max-width: 92%; }
.login form { padding: 28px; border: 0; border-radius: 14px; box-shadow: 0 12px 32px rgba(122, 56, 18, .12); }
.login .button-primary { background: #c2410c; border-color: #c2410c; }
.login .button-primary:hover, .login .button-primary:focus { background: #9a330a; border-color: #9a330a; }
.login #nav a, .login #backtoblog a { color: #7c2d12; }
.login .message { border-left-color: #c2410c; }
.login .button-secondary { color: #7c2d12; border-color: #c2410c; }
.login input[type=text]:focus, .login input[type=password]:focus { border-color: #c2410c; box-shadow: 0 0 0 1px #c2410c; }
';
// Put login-logo.png in the same folder as this file.
$css .= '.login h1 a { background-image: url(' . esc_url_raw( plugins_url( 'login-logo.png', __FILE__ ) ) . '); }';
wp_add_inline_style( 'login', $css );
}
add_action( 'login_enqueue_scripts', 'maple_login_styles' );
/**
* Send the logo to the home page and give it the site name as its text.
*/
function maple_login_header_url() {
return home_url( '/' );
}
add_filter( 'login_headerurl', 'maple_login_header_url' );
function maple_login_header_text() {
return get_bloginfo( 'name' );
}
add_filter( 'login_headertext', 'maple_login_header_text' );
Run this on the lost password and reset screens and you get a branded card, your own logo and your own colors. The login_headertext filter has existed since WordPress 5.2, according to its reference page.

The next block adds a note above the form and renames two buttons. The note hooks into login_form_lostpassword and runs on that screen only. The gettext filter checks that the current page is wp-login.php before it changes a label:
/**
* Add a short note above the lost password form. login_form_lostpassword only fires on that screen.
*/
function maple_lostpassword_note() {
add_filter(
'login_message',
function ( $message ) {
return $message . '<p class="message">' . esc_html__( 'The email can take a few minutes. Check your spam folder if it does not arrive.', 'maple-login' ) . '</p>';
}
);
}
add_action( 'login_form_lostpassword', 'maple_lostpassword_note' );
/**
* Change button labels on wp-login.php only.
*/
function maple_login_strings( $translation, $text, $domain ) {
if ( 'default' !== $domain || empty( $GLOBALS['pagenow'] ) || 'wp-login.php' !== $GLOBALS['pagenow'] ) {
return $translation;
}
$map = array(
'Get New Password' => 'Email me a reset link',
'Save Password' => 'Save new password',
);
return isset( $map[ $text ] ) ? $map[ $text ] : $translation;
}
add_filter( 'gettext', 'maple_login_strings', 10, 3 );
The reset screen picks up the same styles and the new Save new password label. WordPress still fills the field with a generated password and shows a strength meter. Keep your CSS from hiding either one.

Customize the Reset Email
The reset email is part of the page experience, and two filters rewrite it. The retrieve_password_title filter changes the subject line. The retrieve_password_message filter changes the body and receives the reset key and the user object. That lets you rebuild the link.
This code replaces both with plain text that names the site and explains the link. It builds the link with network_site_url, the same way core does, and plain text needs no HTML escaping:
/**
* Rewrite the reset email. WordPress passes in the finished message, the reset key,
* the login name and the user object.
*/
function maple_reset_email_subject( $title, $user_login, $user_data ) {
return sprintf(
/* translators: %s: site name */
__( 'Your %s password reset link', 'maple-login' ),
wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES )
);
}
add_filter( 'retrieve_password_title', 'maple_reset_email_subject', 10, 3 );
function maple_reset_email_message( $message, $key, $user_login, $user_data ) {
$url = network_site_url( 'wp-login.php?login=' . rawurlencode( $user_login ) . '&key=' . $key . '&action=rp', 'login' );
return sprintf(
/* translators: 1: display name, 2: site name, 3: reset URL */
__( "Hi %1\$s,\n\nUse this link to choose a new password for %2\$s:\n\n%3\$s\n\nThe link works once and expires after 24 hours. If you did not ask for it, ignore this email and your password stays the same.\n", 'maple-login' ),
$user_data->display_name,
wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
$url
);
}
add_filter( 'retrieve_password_message', 'maple_reset_email_message', 10, 4 );
WordPress sent the default reset message as plain text on our test site. A plain-text rewrite matches it. An HTML email needs a different mail content type, and that setting affects every message your site sends.

The text says the link works once and expires after 24 hours. We confirmed the first claim on our test site. Opening the same link a second time sent us back to the lost password form with an invalid link error.
The check_password_reset_key reference gives one day as the default lifetime, and the password_reset_expiration filter changes it.
Method 3: Customize the WooCommerce Lost Password Page
WooCommerce serves its own lost password and reset screens inside My Account at /my-account/lost-password/. They use their own templates and hooks. The login hooks from Method 2 do not apply there. Start with hooks because they survive plugin updates better than copied templates.
The template fires woocommerce_before_lost_password_form and woocommerce_before_reset_password_form around the forms. It also runs the intro text and the confirmation text through filters. This plugin code adds a heading, rewrites both messages and loads CSS on the lost-password endpoint only:
<?php
defined( 'ABSPATH' ) || exit;
/**
* Load CSS only on the lost password endpoint (the request form and the reset form share it).
*/
function maple_account_reset_styles() {
if ( ! function_exists( 'is_wc_endpoint_url' ) || ! is_wc_endpoint_url( 'lost-password' ) ) {
return;
}
wp_register_style( 'maple-account-reset', false, array(), '1.0.0' );
wp_enqueue_style( 'maple-account-reset' );
wp_add_inline_style(
'maple-account-reset',
'.woocommerce-ResetPassword { max-width: 460px; margin: 0 auto; padding: 28px; background: #fdf6ee; border-radius: 14px; box-shadow: 0 12px 32px rgba(122, 56, 18, .12); }
.woocommerce-ResetPassword .button { background: #c2410c; color: #fff; border: 0; border-radius: 8px; padding: 12px 22px; }
.woocommerce-ResetPassword .button:hover { background: #9a330a; }
.maple-reset-title { text-align: center; color: #7c2d12; }'
);
}
add_action( 'wp_enqueue_scripts', 'maple_account_reset_styles' );
/**
* Add a heading above the form. The template fires this action before the form opens.
*/
function maple_reset_heading() {
echo '<h2 class="maple-reset-title">' . esc_html__( 'Reset your password', 'maple-woo' ) . '</h2>';
}
add_action( 'woocommerce_before_lost_password_form', 'maple_reset_heading' );
/**
* Reword the intro text. The template prints this value without escaping it again, so escape it here.
*/
function maple_reset_intro() {
return esc_html__( 'Enter the email on your account and we will send you a link to choose a new password.', 'maple-woo' );
}
add_filter( 'woocommerce_lost_password_message', 'maple_reset_intro' );
function maple_reset_confirmation() {
return __( 'Check your inbox for a reset link. It can take a few minutes to arrive.', 'maple-woo' );
}
add_filter( 'woocommerce_lost_password_confirmation_message', 'maple_reset_confirmation' );
/**
* The reset form (the page the email link opens) has its own before action.
*/
function maple_reset_form_heading() {
echo '<h2 class="maple-reset-title">' . esc_html__( 'Choose a new password', 'maple-woo' ) . '</h2>';
}
add_action( 'woocommerce_before_reset_password_form', 'maple_reset_form_heading' );
One detail is easy to miss. The template prints the woocommerce_lost_password_message value without escaping it again. The filter must return escaped text, as the code above does.

When hooks are not enough, copy the template. WooCommerce 11.2 keeps three files in templates/myaccount: form-lost-password.php, lost-password-confirmation.php and form-reset-password.php. The WooCommerce template documentation says to copy a file into yourtheme/woocommerce with the same subfolders and to keep its hooks.
We copied form-lost-password.php into a child theme and wrapped the form in a div with a help line below it. The override loaded at once on our block theme test store, and WooCommerce listed it on its Status screen. Changed lines in the copy looked like this:
<div class="maple-reset"> <form method="post" class="woocommerce-ResetPassword lost_reset_password"> <?php // The rest of the original template stays unchanged. ?> </form> <p class="maple-reset-help"><?php esc_html_e( 'Need help? Reply to any of our emails and a person will answer.', 'maple-woo' ); ?></p> </div>

Method 4: Build Your Own Reset Page
Build your own custom reset password page when you want full control of the markup, a custom form or extra rules. WordPress still does the hard parts. Core checks the key with check_password_reset_key and saves the password with reset_password, and your code only decides what the visitor sees.
A common mistake is to call reset_password with values from the URL. Another is to redirect the reset request before WordPress has saved anything. Neither works.
The reset_password reference shows that the function takes a user object and a plain-text password. Its source never calls check_password_reset_key. Your code must validate first.

Start with the page address, the cookie and the hand-over. Create a page with the slug reset-password and the shortcode [maple_reset_password] in it. This code moves the key from the email link into a cookie and redirects there:
<?php
defined( 'ABSPATH' ) || exit;
define( 'MAPLE_RESET_COOKIE', 'maple-resetpass-' . COOKIEHASH );
/**
* URL of the page that holds the [maple_reset_password] shortcode.
*/
function maple_reset_page_url() {
return home_url( '/reset-password/' );
}
/**
* Read "login:key" from the cookie and check it with core. Returns a WP_User or false.
*/
function maple_reset_user() {
if ( empty( $_COOKIE[ MAPLE_RESET_COOKIE ] ) || false === strpos( $_COOKIE[ MAPLE_RESET_COOKIE ], ':' ) ) {
return false;
}
list( $login, $key ) = explode( ':', wp_unslash( $_COOKIE[ MAPLE_RESET_COOKIE ] ), 2 );
$user = check_password_reset_key( $key, $login );
return is_wp_error( $user ) ? false : $user;
}
function maple_reset_cookie( $value, $expires ) {
setcookie(
MAPLE_RESET_COOKIE,
$value,
array(
'expires' => $expires,
'path' => wp_parse_url( maple_reset_page_url(), PHP_URL_PATH ),
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
)
);
}
/**
* 1. The email link opens wp-login.php?login=...&key=.... Hand a GET request over
* to the front-end page. Never touch a POST here: core still needs it for its own form.
*/
function maple_hand_over_reset_link() {
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] || empty( $_GET['key'] ) || empty( $_GET['login'] ) ) {
return;
}
$value = wp_unslash( $_GET['login'] ) . ':' . wp_unslash( $_GET['key'] );
maple_reset_cookie( $value, 0 );
wp_safe_redirect( maple_reset_page_url() );
exit;
}
add_action( 'login_form_resetpass', 'maple_hand_over_reset_link' );
Hook login_form_resetpass here and leave login_form_rp alone. In WordPress 7.1.3, wp-login.php switches the action to resetpass whenever the URL has a key. The rp hook only fires after core has moved the key into its own cookie.
The check for a GET request also matters. Core’s own reset form posts to the same action, and your hook must not touch that request.
The page itself shows a form only when the key is valid. It sets no-cache headers and a no-referrer policy for the reset page. Messages come from a fixed list. No request data is printed back:
/**
* Keep the page out of caches and stop the browser from sending a referrer.
*/
function maple_reset_page_headers() {
if ( is_page( 'reset-password' ) ) {
nocache_headers();
header( 'Referrer-Policy: no-referrer' );
}
}
add_action( 'template_redirect', 'maple_reset_page_headers', 1 );
/**
* Messages are looked up by a fixed code, so request data is never printed back.
*/
function maple_reset_messages() {
return array(
'empty' => __( 'Enter a new password.', 'maple-reset' ),
'mismatch' => __( 'The two passwords do not match.', 'maple-reset' ),
'short' => __( 'Use at least 12 characters.', 'maple-reset' ),
'expired' => __( 'The form expired. Please try again.', 'maple-reset' ),
'error' => __( 'That password was not accepted. Please try another.', 'maple-reset' ),
);
}
/**
* 2. The shortcode shows the form when core accepts the key, and a notice when it does not.
*/
function maple_reset_shortcode() {
$status = isset( $_GET['maple_status'] ) ? sanitize_key( wp_unslash( $_GET['maple_status'] ) ) : '';
if ( 'done' === $status ) {
return '<p>' . esc_html__( 'Your password has been changed.', 'maple-reset' ) . ' <a href="' . esc_url( wp_login_url() ) . '">' . esc_html__( 'Log in', 'maple-reset' ) . '</a></p>';
}
if ( ! maple_reset_user() ) {
return '<p>' . esc_html__( 'This reset link is invalid or has expired.', 'maple-reset' ) . ' <a href="' . esc_url( wp_lostpassword_url() ) . '">' . esc_html__( 'Request a new one', 'maple-reset' ) . '</a></p>';
}
$messages = maple_reset_messages();
$notice = isset( $messages[ $status ] ) ? '<p class="maple-reset-error" role="alert">' . esc_html( $messages[ $status ] ) . '</p>' : '';
ob_start();
?>
<?php echo $notice; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built from fixed, escaped strings above. ?>
<form class="maple-reset-form" method="post" action="<?php echo esc_url( maple_reset_page_url() ); ?>">
<?php wp_nonce_field( 'maple_set_password', 'maple_nonce' ); ?>
<p>
<label for="maple-pass1"><?php esc_html_e( 'New password', 'maple-reset' ); ?></label>
<input type="password" name="pass1" id="maple-pass1" autocomplete="new-password" minlength="12" required>
</p>
<p>
<label for="maple-pass2"><?php esc_html_e( 'Confirm new password', 'maple-reset' ); ?></label>
<input type="password" name="pass2" id="maple-pass2" autocomplete="new-password" minlength="12" required>
</p>
<p><button type="submit"><?php esc_html_e( 'Save new password', 'maple-reset' ); ?></button></p>
</form>
<?php
return ob_get_clean();
}
add_shortcode( 'maple_reset_password', 'maple_reset_shortcode' );
The last piece saves the password. It checks the nonce and validates the key again.
Then it checks the new password and runs the validate_password_reset action that core uses for password rules. Only then does it call reset_password:
/**
* 3. Save the password. Check the nonce, check the key again, validate, then call core.
*/
function maple_save_new_password() {
if ( ! is_page( 'reset-password' ) || 'POST' !== $_SERVER['REQUEST_METHOD'] || ! isset( $_POST['maple_nonce'] ) ) {
return;
}
$back = maple_reset_page_url();
$nonce = isset( $_POST['maple_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['maple_nonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'maple_set_password' ) ) {
wp_safe_redirect( add_query_arg( 'maple_status', 'expired', $back ) );
exit;
}
$user = maple_reset_user();
if ( ! $user ) {
wp_safe_redirect( $back ); // The page now shows the invalid link notice.
exit;
}
// Passwords are not sanitized. Only remove the slashes WordPress adds.
$pass1 = isset( $_POST['pass1'] ) ? wp_unslash( $_POST['pass1'] ) : '';
$pass2 = isset( $_POST['pass2'] ) ? wp_unslash( $_POST['pass2'] ) : '';
$errors = new WP_Error();
if ( '' === trim( $pass1 ) ) {
$errors->add( 'empty', 'empty' );
} elseif ( ! hash_equals( $pass1, $pass2 ) ) {
$errors->add( 'mismatch', 'mismatch' );
} elseif ( strlen( $pass1 ) < 12 ) {
$errors->add( 'short', 'short' );
}
// Let other plugins add their own password rules, the same hook core uses.
do_action( 'validate_password_reset', $errors, $user );
if ( $errors->has_errors() ) {
$code = $errors->get_error_code();
wp_safe_redirect( add_query_arg( 'maple_status', isset( maple_reset_messages()[ $code ] ) ? $code : 'error', $back ) );
exit;
}
reset_password( $user, $pass1 );
maple_reset_cookie( '', time() - YEAR_IN_SECONDS );
wp_safe_redirect( add_query_arg( 'maple_status', 'done', $back ) );
exit;
}
add_action( 'template_redirect', 'maple_save_new_password' );
We tested this plugin on our test site with a real emailed link. Everything below behaved as intended:
- Mismatched passwords: The page showed “The two passwords do not match.” and saved nothing.
- Short password: A password under 12 characters was rejected by the server, even with the browser check removed.
- Bad nonce: A tampered nonce returned “The form expired” and changed nothing.
- Valid reset: The new password worked, and the page said “Your password has been changed.”
- Reused link: The same link showed “This reset link is invalid or has expired.”

This page has no strength meter, and it leaves the lost password request form to core. Style that form with Method 2.
Extra checks such as a two-factor code belong in the validate_password_reset action.
Stores that want two-factor sign-in can compare WooCommerce two-factor authentication plugins. Our guide to limiting login attempts in WordPress covers brute-force protection. If you would rather not maintain code like this, our WordPress development services team can build it for you.
Test the Reset Flow Before You Publish
Test every reset screen end to end before visitors rely on it. A styled page that cannot save a password is worse than the default one. Use a throwaway account and run the full request, email and save cycle on desktop and mobile.
A reset has more moving parts than most forms. It touches the request form, the mail system, a one-time key and the password itself. A change to any one of them can break the chain. Test the whole chain and not a single screen.
These checks caught real problems while we built the examples:
- Capture the email: Hook the pre_wp_mail filter on a test site and return true to write each message to a file instead of sending it.
- Open the link twice: The second visit must fail. If it works, a key was not cleared.
- Break the form: Submit empty fields, mismatched passwords and a short password. Each must show a clear message.
- Check the cache: Exclude your reset page from page caching, because a cached nonce or message looks like a bug.
- Log in afterwards: Sign in with the new password to prove it was saved.
- Read the email in plain text: A broken link often shows up as a stray character or a missing key.
Repeat the run after each WordPress or WooCommerce update. Reset code touches security functions. A change in core can break a custom page without warning.
Fix Common Reset Page Problems
Most reset page bugs come from one of five causes, and we hit several of them while building the examples above. Each cause has a quick check you can run in a few minutes. Work through these before you blame your theme or your host.
- Your CSS does nothing: Confirm the CSS is attached to the login handle inside login_enqueue_scripts. CSS attached to a handle that is not enqueued on that screen never prints.
- Raw tags appear in the WooCommerce intro: The template prints the woocommerce_lost_password_message value as is. Return text that you already escaped, as the Method 3 code does.
- A custom reset form saves nothing: Our first version posted to admin-post.php, which sits outside the cookie path of the reset page. The key never arrived. Handling the post on the reset page itself fixed it.
- The reset link opens the default form: Your hook is on login_form_rp. The link with a key arrives as login_form_resetpass instead.
- WooCommerce ignores your override: The copy must keep the same subfolders as the plugin. The file belongs in yourtheme/woocommerce/myaccount. The WooCommerce Status screen lists every override it finds and makes a wrong path easy to spot.
If a reset worked yesterday and fails today, compare the plugin and theme versions first. Template files carry a version header. Compare your copy with the core file after every update. If that turns up nothing, check for plugin conflicts on WordPress next.
Which Method Fits Your Site
Pick the method by how much control you need and where your visitors reset passwords. Theme My Login suits themed pages with little work, login hooks suit branding the default screens, and the WooCommerce hooks suit stores. Build your own page only when the first three cannot do the job.
Methods 2 and 4 also work together. We ran both plugins at once, and the styled request form, the custom email and the custom reset page completed a reset without any conflict.
The table below turns that into a quick choice:

| Your situation | Method | Where you work |
|---|---|---|
| You want themed login pages fast | Method 1 | Theme My Login settings |
| You only need brand colors, a logo and clearer labels | Method 2 | Login hooks in a plugin |
| Your customers reset passwords in My Account | Method 3 | WooCommerce hooks or a template copy |
| You need a custom form or extra password rules | Method 4 | Your own page and plugin |
Conclusion
You can customize the WordPress reset password page without risking the reset itself if you keep core in charge of keys and passwords. Use hooks to restyle, a plugin to theme, and your own page only when you need it.
Test the full cycle after every change, from request to a working login. Keep your code in a child theme or small plugin, and re-check it after major WordPress or WooCommerce updates. Our WordPress security checklist covers the wider hardening steps.
Frequently Asked Questions (FAQs)
Q1. Where is the WordPress reset password page?
The default request form is at wp-login.php?action=lostpassword on your site. The new password form opens from the emailed link. WooCommerce stores use /my-account/lost-password/, and Theme My Login uses /lostpassword/ and /resetpass/ by default.
Q2. How do I change the logo on the reset password page?
Add your CSS with wp_add_inline_style on the login handle inside login_enqueue_scripts. Set the logo as a background image on the h1 link. Then use the login_headerurl and login_headertext filters to point it at your home page with your site name.
Q3. How long does a password reset link last?
One day by default, and each link works once. The check_password_reset_key reference names the password_reset_expiration filter for changing the lifetime. Our test confirmed that a used link stops working.
Q4. Can I redirect users after they reset their password?
Core shows a “Your password has been reset” notice with a Log in link, and that is what we saw. Theme My Login sent us to /login/ with a confirmation. With Method 4 you choose the next page yourself in the redirect after reset_password.
Q5. Is it safe to call reset_password myself?
Only after check_password_reset_key returns a user, and only with a password taken from your own form. The reference says reset_password does not check the key. Never pass values from the URL straight into it.
Q6. Why does my reset email not arrive?
Test the sending first. On a test site, the pre_wp_mail filter shows whether WordPress built the message. If the message exists but never lands, the problem sits in mail delivery on your host and the reset page is fine.
