UPDATE: The plugin from the tutorial is now available on the WordPress plugin repository – WP Delete User Accounts
Some websites allow a user to delete his or her account, and many don’t. Personally, I like having the option for deleting my accounts (i.e. social media), and I’m sure many other people do, because it gives us a sense of “control.”
If your website runs on WordPress, you may know that there is no built-in method for allowing your users to delete their accounts. When an account is created, either by the admin or the user, the only way for that account to be deleted is for an administrator to manually delete the account via the WordPress admin, or by directly removing it from the database (not recommended, unless you know what you’re doing).
In this tutorial, we’ll set up a method for allowing our users to delete their accounts on our WordPress websites. Since deleting a user’s account is serious business, for which there is no return, unless you restore a backup of the database, we’ll want to implement some extra security features. Here’s what we’ll cover:
- Create a shortcode that will output the delete-account button. This will be for allowing users to delete their accounts on the frontend, such as through an account page you have set up.
- The delete-account button will prompt the user to confirm his or her action through a modal window that requires user input.
- After the prompt is submitted successfully, we’ll send the request to delete the account via AJAX. When the deletion is processed, we’ll send a response, display it to the user, then redirect the user to our home page.
- Add the delete button on the admin profile page. This will be for deleting an account while in the admin.
If you want to download or look at the code before getting started (or you just want the code), you can grab it from GitHub.
Create the Button
First, let’s start out easy by covering the button we’ll be using to submit the request. Under the “includes” directory, there’s a file called functions.php, which is not to be mistaken for your theme’s functions.php. This file will hold the plugin’s helper functions, which, as of now, is only one function. Here’s that function:
function wp_delete_user_account_delete_button( $button_text = '' ) {
// Bail if user is logged out
if ( ! is_user_logged_in() ) {
return;
}
// Bail to prevent administrators from deleting their own accounts
if ( current_user_can( 'manage_options' ) ) {
return;
}
// Defauly button text
if ( $button_text == '' ) {
$button_text = __( 'Delete My Account', 'wp-delete-user-accounts' );
}
// Button
printf( '<button id="delete-my-account">%s</button>', $button_text );
}
With this function, we can output a button with whatever text we wish. The default text is “Delete My Account.” As you may be able to see, the function outputs the button only when the user is logged in and is not an administrator, because we do not want admins to be able to delete their accounts, and, potentially, lose access to the dashboard.
Create the Shortcode
Next up, let’s tackle the shortcode we can use to display the button on the front-end. This will be helpful if you’re using any type of plugin that creates front-end profiles for your users to manage. Also under the “includes” directory, you’ll find frontend.php. Here’s the gist of what we’re aiming for:
public function init() {
add_shortcode( 'wp_delete_user_accounts', array( $this, 'wp_delete_user_accounts' ) );
}
/**
* Render [wp_delete_user_accounts] shortcode
*/
public function wp_delete_user_accounts( $atts ) {
// Show nothing if user is logged out
if ( ! is_user_logged_in() ) {
return '';
}
// Bail to prevent administrators from deleting their own accounts
if ( current_user_can( 'manage_options' ) ) {
return '';
}
// Attributes
extract( shortcode_atts( array(
'label' => __( 'This will cause your account to be permanently deleted. You will not be able to recover your account.', 'wp-delete-user-accounts' ),
'button_text' => '',
), $atts ) );
ob_start();
/**
* Template path
*
* @param (array) $atts - Shortcode attributes
*/
include_once apply_filters( 'wp_delete_user_accounts_shortcode_template', WP_DELETE_USER_ACCOUNTS_TEMPLATE_DIR . 'shortcode-wp_delete_user_accounts.php', $atts );
return ob_get_clean();
}
Above, we are registering the [wp_delete_user_accounts] shortcode, then rendering it’s output in the wp_delete_user_accounts()
method. Again, this shortcode renders only when the user is logged in and is not an administrator.
This shortcode accepts a few arguments so that the output can be customized to an extent. Those arguments are “label” (warning/message to display above the button) and “button_text” (text for the button). The extract()
function “extracts” each of these array elements, and turns the key into a variable equal to the value. The shortcode()
function combines an array of default attributes with the attributes added to the shortcode by the user.
Lastly, we start an output buffer (stores our output instead of printing it), include the shortcode’s template file (this is filterable using wp_delete_user_accounts_shortcode_template
), and return the output. When you create shortcodes, remember that you do not output the content from the callback function. Instead, you return the content, and WordPress will output it for you.
Create the Warning Alert
As I mentioned earlier, deleting user accounts is always serious stuff, even when administrators do it. Allowing users to delete their own accounts can lead to headache for you as an administrator. Ultimately, when the user deletes his or her account, the only way to get it back is to restore your website’s database to a point from before the user deleted the account. If you run a website with a database that is frequently added to (i.e. e-commerce, membership, etc.), this is going to be a real hassle. You could always save the user’s data to another place in the database before deleting it, but I won’t cover that in this tutorial. For now, we just want to show a warning to the user, then prompt the user to confirm his or her action.
To do this, we’ll be using a really slick jQuery plugin called Sweet Alert. In fact, Sweet Alert is the main reason for this post; I was looking for a good excuse to test out this plugin, and the idea for this post came to mind.
When the delete button is clicked, the users will see a warning, alerting them that their action is permanent, and their accounts cannot be recovered. Then, the alert will ask for some input, which requires that the user enter the word “DELETE,” in all capital letters. Here’s what the warning will look like, and how it should progress:
Here’s the JavaScript/jQuery for the warning, which is found at “/asstes/js/wp-delete-user-accounts.js.”
jQuery(document).ready(function($) {
/**
* Process request to dismiss our admin notice
*/
$('#delete-my-account').click(function(e) {
e.preventDefault();
// Set the ajaxurl when not in the admin
if ( wp_delete_user_accounts_js.is_admin != 'true' ) {
ajaxurl = wp_delete_user_accounts_js.ajaxurl;
}
// Data to make available via the $_POST variable
data = {
action: 'wp_delete_user_account',
wp_delete_user_accounts_nonce: wp_delete_user_accounts_js.nonce
};
// Send the AJAX POST request
var send = function() {
$.post( ajaxurl, data, function(response) {
if ( typeof( response.status ) != 'undefined' && response.status == 'success' ) {
// Account deleted
swal(
{
title: response.title,
text: response.message,
type: 'success',
timer: 6000
},
function() {
window.location.href = wp_delete_user_accounts_js.redirect_url;
}
);
} else { // Error occurred
swal( response.title, response.message, 'error' );
}
} );
return false;
}
// Main prompt
swal(
{
title: wp_delete_user_accounts_js.alert_title,
text: wp_delete_user_accounts_js.alert_text,
type: 'input',
animation: 'slide-from-top',
showCancelButton: true,
closeOnConfirm: false,
confirmButtonText: wp_delete_user_accounts_js.confirm_text,
confirmButtonColor: '#EC5245',
disableButtonsOnConfirm: true,
// showLoaderOnConfirm: true,
// confirmLoadingButtonColor: '#f5f7fa',
inputPlaceholder: wp_delete_user_accounts_js.input_placeholder
},
function(input) {
if ( input !== 'DELETE' ) {
// Input was not correct
swal(
{
title: wp_delete_user_accounts_js.incorrect_prompt_title,
text: wp_delete_user_accounts_js.incorrect_prompt_text,
type: 'error',
showLoaderOnConfirm: false,
}
);
return;
}
// Processing modal
swal(
{
title: wp_delete_user_accounts_js.processing_title,
text: wp_delete_user_accounts_js.processing_text,
type: 'info',
confirmLoadingButtonColor: '#f5f7fa',
showConfirmButton: false
}
);
// Wait 2 seconds and send request
setTimeout(send, 2000);
}
);
});
});
There’s really not much to this code, as most of it is configuring different Sweet Alert modals. The AJAX request configuration is saved to the send variable, and there’s a two second delay in sending the request after the user successfully submits the prompt. The way this process goes is: 1.) user gets a warning about deleting their account; 2.) if the user wants to delete, he or she must enter the text required by the prompt; 3.) if the prompt submission is successful, the user will see a “Processing…” modal; 4.) two seconds later, the AJAX request is sent and processed, and the user sees a result modal; 5.( if the request was successful (user deleted), the user will see the success modal, then the page will redirect to the home page after six seconds, or if the user clicks “OK” in the success modal.
Add Delete Button to Admin
Now, let’s add the same button to the edit-profile page in the WordPress admin. This will allow users to be able to delete their account from the admin, such as when you don’t have a front-end profile area.
public function init() {
add_action( 'show_user_profile', array( $this, 'delete_account_button_admin' ), 99 );
}
public function delete_account_button_admin() {
// Bail to prevent administrators from deleting their own accounts
if ( current_user_can( 'manage_options' ) ) {
return;
}
printf( '<h3>%s</h3>', __( 'Delete Account', 'wp-delete-user-accounts' ) );
printf( '<p>%s</p>', __( 'This will cause your account to be permanently deleted. You will not be able to recover your account.', 'wp-delete-user-accounts' ) );
wp_delete_user_account_delete_button();
}
The show_user_profile hook will display our content at the bottom of the edit-profile page, just before the save button. There, we will display a simple headline and message to let the user know what the button does. After that, the process will be the same as with the shortcode.
Enqueueing the Assets
Almost there! We need to enqueue our JS and CSS files. If we don’t, none of the preceding steps will work.
public function hooks() {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueues' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueues' ) );
}
/**
* Enqueue the assets
*/
public function enqueues() {
// Bail if user is logged out
if ( ! is_user_logged_in() ) {
return;
}
// Bail to prevent administrators from deleting their own accounts
if ( current_user_can( 'manage_options' ) ) {
return;
}
global $post;
$vars = array(
'alert_title' => __( 'Whoa, there!', 'wp-delete-user-accounts' ),
'alert_text' => __( 'Once you delete your account, there\'s no getting it back. Make sure you want to do this.', 'wp-delete-user-accounts' ),
'confirm_text' => __( 'Yep, delete it', 'wp-delete-user-accounts' ),
'incorrect_prompt_title' => __( 'Error', 'wp-delete-user-accounts' ),
'incorrect_prompt_text' => __( 'Your confirmation input was incorrect.', 'wp-delete-user-accounts' ),
'processing_title' => __( 'Processing...', 'wp-delete-user-accounts' ),
'processing_text' => __( 'Just a moment while we process your request.', 'wp-delete-user-accounts' ),
'input_placeholder' => __( 'Confirm by typing DELETE', 'wp-delete-user-accounts' ),
'redirect_url' => home_url(),
'nonce' => wp_create_nonce( 'wp_delete_user_accounts_nonce' ),
);
if ( is_admin() ) {
if ( get_current_screen()->base == 'profile' ) {
wp_enqueue_style( 'wp-delete-user-accounts-css', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/css/wp-delete-user-accounts.css', '', WP_DELETE_USER_ACCOUNTS_VERSION );
wp_enqueue_script( 'sweetalert-js', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/js/sweetalert.min.js', array( 'jquery' ), WP_DELETE_USER_ACCOUNTS_VERSION, true );
wp_enqueue_script( 'wp-delete-user-accounts-js', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/js/wp-delete-user-accounts.js', array( 'jquery', 'sweetalert' ), WP_DELETE_USER_ACCOUNTS_VERSION, true );
wp_localize_script( 'wp-delete-user-accounts-js', 'wp_delete_user_accounts_js', array_merge( $vars, array( 'is_admin' => 'true' ) ) );
}
} elseif ( has_shortcode( $post->post_content, 'wp_delete_user_accounts' ) ) {
wp_enqueue_style( 'wp-delete-user-accounts-css', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/css/wp-delete-user-accounts.css', '', WP_DELETE_USER_ACCOUNTS_VERSION );
wp_enqueue_script( 'sweetalert-js', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/js/sweetalert.min.js', array( 'jquery' ), WP_DELETE_USER_ACCOUNTS_VERSION, true );
wp_enqueue_script( 'wp-delete-user-accounts-js', WP_DELETE_USER_ACCOUNTS_PLUGIN_URL . 'assets/js/wp-delete-user-accounts.js', array( 'jquery', 'sweetalert' ), WP_DELETE_USER_ACCOUNTS_VERSION, true );
wp_localize_script( 'wp-delete-user-accounts-js', 'wp_delete_user_accounts_js', array_merge( $vars, array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) ) );
}
}
Here, we’re hooking into both wp_enqueue_scripts and admin_enqueue_scripts, and using the same callback.
Within the callback, we again check to see if the user is logged in and an administrator, then exit if they are. Then, we assemble an array of variables that will be available to our JavaScript. This array is of variables that are to be available whether we are in the admin or not.
Next, we check to see if we’re in the admin. If so, we determine if the current page is the edit-profile screen because we don’t want to load the extra files throughout the admin – only when we need them. Once those requirements are met, we tell WordPress to load our main stylesheet (Sweet Alerts CSS and styling for our delete button), the Sweet Alert script, and our main JavaScript file. Then, we localize our main JS file by creating an admin-specific array of variables, and merging that array with the array we created of standard variables.
If we are not in the admin, we then check to see if the post we’re viewing contains our shortcode. Again, we want to load our CSS and JS only when needed. Since the button isn’t present on posts without the shortcode, we don’t want to load the files. If the shortcode is present, we load our files, and localize the main JS file in the same way as in the admin.
Process the AJAX Request
Lastly, we need to actually tell WordPress what to do when the request is submitted. Everything is in place and queued up. Now, we have to do a security check, delete the user, and send a response back to our main JS file so that it can display a result to the user.
public function init() {
add_action( 'wp_ajax_wp_delete_user_account', array( $this, 'process' ) );
}
/**
* Process the request
* @todo Setting for reassigning user's posts
*/
public function process() {
// Verify the security nonce and die if it fails
if ( ! isset( $_POST['wp_delete_user_accounts_nonce'] ) || ! wp_verify_nonce( $_POST['wp_delete_user_accounts_nonce'], 'wp_delete_user_accounts_nonce' ) ) {
wp_send_json( array(
'status' => 'fail',
'title' => __( 'Error!', 'wp-delete-user-accounts' ),
'message' => __( 'Request failed security check.', 'wp-delete-user-accounts' )
) );
}
// Don't permit admins to delete their own accounts
if ( current_user_can( 'manage_options' ) ) {
wp_send_json( array(
'status' => 'fail',
'title' => __( 'Error!', 'wp-delete-user-accounts' ),
'message' => __( 'Administrators cannot delete their own accounts.', 'wp-delete-user-accounts' )
) );
}
// Get the current user
$user_id = get_current_user_id();
// Get user meta
$meta = get_user_meta( $user_id );
// Delete user's meta
foreach ( $meta as $key => $val ) {
delete_user_meta( $user_id, $key );
}
// Destroy user's session
wp_logout();
// Delete the user's account
$deleted = wp_delete_user( $user_id );
if ( $deleted ) {
// Send success message
wp_send_json( array(
'status' => 'success',
'title' => __( 'Success!', 'wp-delete-user-accounts' ),
'message' => __( 'Your account was successfully deleted. Fair well.', 'wp-delete-user-accounts' )
) );
} else {
wp_send_json( array(
'status' => 'fail',
'title' => __( 'Error!', 'wp-delete-user-accounts' ),
'message' => __( 'Request failed.', 'wp-delete-user-accounts' )
) );
}
}
Here’s what going on:
- Check to see if the security nonce is good, and send an error response if not.
- Check to see if the user is an admin, and send an error response if yes. This is not necessary because the button will not be displayed, and the required files will not be added, if the user is an admin. I included this check just to add another layer, and reemphasize that we don’t want admins to delete their own accounts.
- Get the current user, and its user meta.
- Loop through all user meta, and delete it from the database. This part is getting rid of all the user’s information, not just the user.
- Log the user out of his or her account.
- Delete the user from the database.
- If the user was successfully deleted, send a success response. Otherwise, send an error response.
The End!
This tutorial walked you through the plugin I created for allowing users of a WordPress website, excluding administrators, to delete their account. You can download the plugin from GitHub, and use it on your site. If you have any questions about the plugin or this tutorial, drop them in the comments below.
Wiebke Schneider says
Hi Ren.
Thanks alot for the development of the plugin. I ‘m using it on my new website.
The very understandable explanation of how the plugin works helped me a lot. That’s why I was thinking about if it would be possible to prompt for the user’s password for confirmation instead of asking for typing in DELETE. This would be an additional security check. Let me now your feedback.
Thanks in advance for the feedback.
Best Regards,
Wiebke Schneider
Shaul Solomon says
Hey Ren.
Fantastic tutorial.
I was wondering if it was possible to allow the button to be displayed wherever I want (not only on the user account on the dashboard).
In my case, I have a woocommerce website, and would like to allow the client to delete their account on the http://www.mywebsite.com/account page.
How would you recommend I do that?
Thank you
Björn says
Hello,
first of all thank you for sharing your code with us.
I have a general question. In the past I used a plugin to add script to my functions.php to avoid previous changes to be overwritten by updates. That won’t work in this case, because the script goes to other different files. Would creating a child theme prevent me from loss of additional script in this particular case?
Thank you again and best regards,
-Björn
Ren says
Hi, Bjorn. I’m not sure what you mean when you say you can’t use a plugin, because you can enqueue scripts from anywhere using a plugin, even if they’re not part of that plugin. A child-theme will accomplish the same thing, though.
Alex says
Hi Ren,
I try to use with woocommerce plugin on my account page with customer role but the button not work, appear but the button can’t open anything.
WP 4.7 and Woocommerce 2.6.9
Here is the shortcode:
[wp_delete_user_accounts label=”” button_text=”This will cause your account to be permanently deleted. You will not be able to recover your account.”]
Can u help me please where i’m wrong?
Thank you
Pierre Bresson says
Hi,
The plugin is not working for me.
Maybe it’s because I’m using s2member.
Regards,
Ren says
When you try to delete an account, are you logged into that account as an administrator?
Gerardo says
I already installed your plugin and activate it, but nothing happens. Please some help. Thank you
Ren says
Have you looked at the edit-profile page, or inserted the shortcode? Remember, also, if you’re logged in as an administrator, you won’t see anything because the plugin doesn’t let administrators delete their own accounts.
Gerardo says
So, i put the shortcode and the button appears but no action after that.
Jean-Philippe says
Hi Ren. I’ve just created a new website with few subscribers and I was indeed wondering how can one delete his/her account. So your plugin comes in handy, thank you!
By the way, do you plan to push this plugin to the WordPress Plugin Repository? Same question for your great plugin released with this post: http://www.engagewp.com/display-calls-action-new-blog-comments?
If no, how can I be notified of an update with those plugins hosted on Github?
Ren says
Hey, Jean-Philippe. I’m glad you’re liking the plugins! I’ve given some thought to putting them on the WP repo, but haven’t gotten around to it. I’ll likely do so sometime soon, and update the posts after they’re published.
Jean-Philippe says
Great news, keep up the good work!
Ren says
Hey, Jean-Philippe. Just wanted to let you know that I got both plugins up on the plugin repository. Here are the links:
https://wordpress.org/plugins/wp-delete-user-accounts/
https://wordpress.org/plugins/after-comment-prompts/