2026年11月12日
2026年11月12日
Customize the WordPress Password Reset Email: Subject and Body Filters
This article is a localized edition of the Japanese original. Read the Japanese original
You can change the subject, body and headers of the WordPress password reset email with core filters. The code in this article was checked against the WordPress 6.9.4 source (retrieve_password() in wp-includes/user.php) for argument names and order (as of November 2026).
Put the code in your child theme's functions.php or in a PHP file in wp-content/mu-plugins/. If you put it in the parent theme's functions.php, a theme update will erase it.
What the default email contains
When someone submits the "Lost your password?" form on the login screen, retrieve_password() builds the email as follows.
| Item | Default content |
|---|---|
| Subject | [Site Name] Password Reset (translated on sites in other languages) |
| Body | Site name, username, a note that the email can be ignored if the request was a mistake, the reset URL, and (when requested while logged out) the requester's IP address |
| Recipient | The user's email address |
| Language | The user's language setting (or the site language if none is set) |
| Link expiration | 1 day (changeable with the password_reset_expiration filter) |
The body is built after switching to the user's language, so on a Japanese site the text is already in Japanese. What people usually want to change is the wording, the signature or the support contact.
Filters you can use
| Filter | What it changes | Arguments |
|---|---|---|
retrieve_password_title |
Subject | $title, $user_login, $user_data |
retrieve_password_message |
Body | $message, $key, $user_login, $user_data |
retrieve_password_notification_email |
Recipient, subject, body and headers together (WordPress 6.0 and later) | $defaults (array), $key, $user_login, $user_data |
password_reset_expiration |
How long the reset link stays valid (seconds) | $expiration |
$user_data is a WP_User object. When your callback takes two or more arguments, pass the fourth argument of add_filter() (the number of accepted arguments). Without it, only the first argument is passed.
Step 1: Change the subject
<?php
// Change the subject of the password reset email
add_filter( 'retrieve_password_title', function ( $title, $user_login, $user_data ) {
$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
return sprintf( '[%s] Instructions for resetting your password', $site_name );
}, 10, 3 );
Get the site name with get_option( 'blogname' ). It is HTML-escaped when saved, so decode it with wp_specialchars_decode() before use, as core does.
Step 2: Change the body
If you rebuild the body from scratch, you have to build the reset URL yourself. Building it in the same form as core keeps it working even when, for example, a login name ends with a period.
<?php
// Change the body of the password reset email
add_filter( 'retrieve_password_message', function ( $message, $key, $user_login, $user_data ) {
$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
// Reset URL in the same form as core (login is not the last parameter)
$reset_url = network_site_url(
'wp-login.php?login=' . rawurlencode( $user_login ) . '&key=' . $key . '&action=rp',
'login'
);
$reset_url .= '&wp_lang=' . get_user_locale( $user_data );
$lines = array(
'Dear ' . $user_data->display_name . ',',
'',
'We received a request to reset your password on ' . $site_name . '.',
'Open the following URL and set a new password.',
'',
$reset_url,
'',
'This URL is valid for 24 hours.',
'If you did not make this request, please discard this email. Your password will not change.',
'',
'--',
$site_name,
home_url( '/' ),
);
return implode( "\r\n", $lines );
}, 10, 4 );
Keep the following in mind.
- If you leave the URL out of the body, the user cannot reset the password.
- If this filter returns an empty string, the email is not sent (core behavior).
- The example above omits the requester's IP address that the default body includes. If you want to keep it, append your text to the original
$messageinstead. - If you set the expiration to something other than 24 hours, update the text in the body to match (Step 4).
If you only want to add a line before or after the default body, concatenate instead of rebuilding it.
<?php
// Append a signature to the end of the default body
add_filter( 'retrieve_password_message', function ( $message ) {
return $message . "\r\n--\r\nContact: support@example.com\r\n";
} );
Step 3: Change headers or the recipient together (WordPress 6.0 and later)
retrieve_password_notification_email receives an array of to, subject, message and headers, and returns an array. It runs after the subject and body filters, which makes it a good place to add headers.
<?php
// Set a Reply-To address and send as an HTML email
add_filter( 'retrieve_password_notification_email', function ( $email, $key, $user_login, $user_data ) {
$headers = array();
$headers[] = 'Reply-To: support@example.com';
$headers[] = 'Content-Type: text/html; charset=UTF-8';
$email['headers'] = $headers;
$email['message'] = nl2br( esc_html( $email['message'] ) );
return $email;
}, 10, 4 );
To send an HTML email, you need to rebuild the whole body as HTML. The example above is the minimal form: it escapes the plain-text body and turns line breaks into <br />. The reset URL is shown as plain text, not a link, so if you want a link, build an a element with esc_url() when you compose the body. Considering spam filtering and how mail clients display messages, keeping the email as plain text is also a reasonable choice.
Step 4: Change how long the link is valid
<?php
// Make the password reset link valid for 2 hours
add_filter( 'password_reset_expiration', function () {
return 2 * HOUR_IN_SECONDS;
} );
If you shorten the period, also fix "24 hours" in the body from Step 2.
Step 5: Also change the admin notification for new user registrations
This is a different email from the password reset, but people often want to change its wording the same way, so it is included here. Change the admin notification with wp_new_user_notification_email_admin and the email to the registered user with wp_new_user_notification_email (both take $email, $user, $blogname).
<?php
// Change the wording of the admin notification sent when a new user registers
add_filter( 'wp_new_user_notification_email_admin', function ( $email, $user, $blogname ) {
// Core runs sprintf( subject, site name ) on the subject afterwards, so keep %s
$email['subject'] = '[%s] New user registration: ' . $user->user_login;
$email['message'] = sprintf(
"A new user has registered.\r\n\r\nUsername: %s\r\nEmail: %s\r\n\r\nUser list: %s\r\n",
$user->user_login,
$user->user_email,
admin_url( 'users.php' )
);
return $email;
}, 10, 3 );
After this filter, core passes the admin notification subject through sprintf( subject, site name ). If you write %s in the subject, the site name is inserted there.
Step 6: Send a test and check
- While logged out (in another browser or a private window), open
wp-login.php?action=lostpassword. - Enter the username or email address of a test user and submit.
- Check the subject and body of the email you receive, then open the URL and confirm you can set a new password.
Each request creates a new key, and the URL in earlier emails stops working. When testing, open the URL from the most recent email.
If the email does not arrive at all, the problem is sending, not the wording. Check your server's mail settings or SMTP settings first.
Troubleshooting
| Symptom | What to check |
|---|---|
| The subject or body does not change | Whether the file with the code is loaded (is the active theme the child theme, did you put it in mu-plugins), and whether a plugin using the same filter overrides it later |
$key or other values are empty in the body |
Whether you set the fourth argument of add_filter() (the number of accepted arguments) |
| Opening the URL shows an "invalid key" message | Whether you opened the URL from an older email, whether the link has expired, and whether the mail client wrapped the URL in the middle |
| Emails stopped being sent | Whether retrieve_password_message returns an empty string |
Summary
- Change the subject with
retrieve_password_title, the body withretrieve_password_message, and headers or the recipient withretrieve_password_notification_email(6.0 and later). - When you rebuild the body, build the reset URL in the same form as core and do not forget to include it.
- Change the expiration with
password_reset_expiration, and update the text in the body to match.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)