Skip to content

Instantly share code, notes, and snippets.

@philipnewcomer
Last active February 11, 2023 19:05
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save philipnewcomer/59a695415f5f9a2dd851deda42d0552f to your computer and use it in GitHub Desktop.
Save philipnewcomer/59a695415f5f9a2dd851deda42d0552f to your computer and use it in GitHub Desktop.
Generate a unique username in WordPress
<?php
/**
* Recursive function to generate a unique username.
*
* If the username already exists, will add a numerical suffix which will increase until a unique username is found.
*
* @param string $username
*
* @return string The unique username.
*/
function generate_unique_username( $username ) {
static $i;
if ( null === $i ) {
$i = 1;
} else {
$i++;
}
if ( ! username_exists( $username ) ) {
return $username;
}
$new_username = sprintf( '%s-%s', $username, $i );
if ( ! username_exists( $new_username ) ) {
return $new_username;
} else {
return call_user_func( __FUNCTION__, $username );
}
}
@marounmelhem
Copy link

marounmelhem commented Apr 30, 2018

Perfect thank you! I also added sanitize_title to remove spacing/extra characters:

function generate_unique_username( $username ) {

	$username = sanitize_title( $username );

	static $i;
	if ( null === $i ) {
		$i = 1;
	} else {
		$i ++;
	}
	if ( ! username_exists( $username ) ) {
		return $username;
	}
	$new_username = sprintf( '%s-%s', $username, $i );
	if ( ! username_exists( $new_username ) ) {
		return $new_username;
	} else {
		return call_user_func( __FUNCTION__, $username );
	}
}

@rianrismawati
Copy link

Where can I put this code?

I really need help, thankyou

@KiwiKilian
Copy link

@marounmelhem You may want to use sanitize_user( $username ) instead of sanitize_title( $username ).

@brian-stinar
Copy link

brian-stinar commented May 27, 2019

My final, minor improvement on making sprintf more explicit and including @KiwiKilian 's suggestion.

function generate_unique_username( $username ) {

	$username = sanitize_user( $username );

	static $i;
	if ( null === $i ) {
		$i = 1;
	} else {
		$i ++;
	}
	if ( ! username_exists( $username ) ) {
		return $username;
	}
	$new_username = sprintf( '%s-%s', $username, $i );
	if ( ! username_exists( $new_username ) ) {
		return $new_username;
	} else {
		return call_user_func( __FUNCTION__, $username );
	}
}

Nice approach @philipnewcomer.

@mullet456
Copy link

mullet456 commented Dec 28, 2019

@brian-stinar

Been looking at your final code from May 27. Not sure if it's the best approach as it could perhaps introduce a vulnerability in so far as if a malicious person were to register an account lets say using the username "Bob". The above code would check if username "Bob" already exists, lets say the username already exists. So the code loops though adding "+1" until it finds a unique username.

So it eventually finds a unique username of say "Bob-10". The malicious person then tries to register another account again with the username "Bob". This time the function loop gives gives the malicious person a username of "Bob-11".

Now the malicious person knows how the usernames are being formed (by incrementing +1 each time).

Now the malicious person knows other people have registered accounts with the following usernames on your website:

Bob-1
Bob-2
Bob-3
Bob-4
Bob-5
Bob-6
Bob-7
Bob-8
Bob-9

This would seem to make it easier for the malicious person to run targeted brute force attacks now that he/she knows the usernames for the other accounts, all they need do now is brute force the password for the given username.

A better method might be to take the username provided and automatically add some random characters to it. This prevents malicious people from obtaining usernames for accounts on your website as described above. PHP code something like this...

``

    function generateRandomString($username){

//specify characters to be used in generating random string, do not specify any characters that wordpress does not allow in teh creation of usernames.

$characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ-_";

//get the total length of specified characters to be used in generating random string

$charactersLength = strlen($characters);

//declare a string that we will use to create the random string 

$randomString = '';


//set random string length to 11 characters, can change if you don't like the number 11

for ($i = 0; $i < 11; $i++) {

//generate random characters

$randomCharacter = $characters[rand(0, $charactersLength - 1)];

//add the random characters to the random string

$randomString .=  $randomCharacter;


};

	


//wordpress username max 60 characters in length, so take first 60 character of final username string, 
//assuming some douche has a really long username.... Can change to a lower number if you don't like the number 60

$randomUsername = substr($username."_".$randomString , 0 , 60 );


//sanitize_user, just in case 

$sanRandomUsername = sanitize_user($randomUsername);




//check if randomUsername already exsists....you never know..and also check that randomUsername contains Uppercase/Lowercase/Intergers

if ( (! username_exists( $sanRandomUsername )) && (preg_match('([a-zA-Z].*[0-9]|[0-9].*[a-zA-Z])', $sanRandomUsername)==1) ) {

//return the unique username if it does not exist 

return $sanRandomUsername;

}else {

// if the username already exists or username does not contain Uppercase/Lowercase/Intergers ,call generateRandomString function again 

	
return call_user_func("generateRandomString", $username );

}		

}//end of generateRandomString function



//call generateRandomString function and pass username which we will use to generate part if the RandomUsername


$sanRandomUsername = generateRandomString($username);

//display UNIQUE & Randomly generated username

echo $sanRandomUsername;

``

Now if any person registers an account lets say using the username "Bob" they will receive a randomly generated username that is unique for example "Bob_1BXssxYuHhL".

@brian-stinar
Copy link

@mullet456 The two points that make this not as much of a concern for me are:
1.) For me this was an internal php routine that users didn't call (I wrote a user loader to migrate eCommerce data over in batch.)
2.) This is the same vulnerability that exists within the password reset page within WordPress by default. That page itself will tell you if a username exists or not.

However, your suggestion is still valid. What I'd do here is to replace the $i = 1; and i++; assignments with an i = rand() call that pays attention to the sixty character limit on usernames and probably added four zero padded random integers. I probably am not going to do this on my production system running this. Ideally, this would be _random-characters too. It's a balance between security (using all sixty characters) and convenience (not making a user type in sixty characters.)

I might modify this gist to include your suggestion when I'm on a computer (and not on my phone) and add more error checking for the length. But that's how I'd approach this. If you beat me to the modification, go for it.

@mullet456
Copy link

mullet456 commented Dec 28, 2019

@mullet456 The two points that make this not as much of a concern for me are:
1.) For me this was an internal php routine that users didn't call (I wrote a user loader to migrate eCommerce data over in batch.)
2.) This is the same vulnerability that exists within the password reset page within WordPress by default. That page itself will tell you if a username exists or not.

However, your suggestion is still valid. What I'd do here is to replace the $i = 1; and i++; assignments with an i = rand() call that pays attention to the sixty character limit on usernames and probably added four zero padded random integers. I probably am not going to do this on my production system running this. Ideally, this would be _random-characters too. It's a balance between security (using all sixty characters) and convenience (not making a user type in sixty characters.)

I might modify this gist to include your suggestion when I'm on a computer (and not on my phone) and add more error checking for the length. But that's how I'd approach this. If you beat me to the modification, go for it.

updated my comment above, with solution. In regards to a password reset page if someone enters details to reset a password the page should just display a message stating something like "if there is an account registered with those details and email will have been sent with a password reset link "

@brian-stinar
Copy link

@mullet456 the white space on your above is all messed up and makes it hard to read. It looks like you accidentally used tabs instead of spaces, and GitHub is mangling it.

@mullet456
Copy link

@mullet456 the white space on your above is all messed up and makes it hard to read. It looks like you accidentally used tabs instead of spaces, and GitHub is mangling it.

@brian-stinar i have checked it on Edge, Chrome and Firefox and it is displaying ok on them for me both logged in and logged out.

@brian-stinar
Copy link

brian-stinar commented Dec 28, 2019

@mullet456 Weird, everything for me is almost indented at the same level of indent, and then there are extra spaces around everything... Maybe this is just me, but here's what I'm seeing on both Chrome and Safari on my Mac:

messed_up_formatting

@mullet456
Copy link

@mullet456 Weird, everything for me is almost indented at the same level of indent, and then there are extra spaces around everything... Maybe this is just me, but here's what I'm seeing on both Chrome and Safari on my Mac:

messed_up_formatting

Indents removed

@alexbrinkman0
Copy link

I like this solution (with updates) a lot. For me the vulnerability of a malicious actor discovering username patterns isn't so much an issue because the users will never actually see their usernames. I'm never showing them their usernames and explicitly asking them for their email address on login but I do need a good way to generate unique usernames when they register (users are only supplying their name and emails on registration).

I do agree about the native security oversight on the default WP login form - that is something that has bothered for me awhile about WP.

The final function you've put together is perfect. Thanks guys!

@rongenius757
Copy link

What WordPress folder should I upload this php file into?

@brian-stinar
Copy link

@rongenius757 you put it wherever you need to use it - probably inside your plugin code. Send me a message if you need some help on this after Google'ing my name + city.

@everyone else - WordPress already exposes usernames in a bunch of ways. Here's my favorite: https://wordpress.org/support/topic/rest-api-exposed-user-data-for-all-users/

@poyo12
Copy link

poyo12 commented Jul 4, 2021

@brian-stinar can you help me with this please? I need it to autogenerate username with digits only. The user can only logins with the username name and not emails.

@brian-stinar
Copy link

@poyo12 Here's the free help version:

` function generateRandomString($username = null){

//specify characters to be used in generating random string, do not specify any characters that WordPress does not allow in the creation of usernames.

$characters = "0123456789";
.
.
... everything else the same except for this...
for ($i = 0; $i < 60; $i++) {

`
and then call it with no parameters, like generateRandomString(). Might be better to cut out all reference to $username, if you never, ever want the user to pass in what they'd like ("007" or anything...) This depends on how flexible / reusable you want this code. I'm not going to rewrite this portion during the free help session.

Since that's the first part of the username generation routine. Then, you need to disable email-based logins, probably like this:

remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
https://www.wpbeginner.com/plugins/how-to-disable-login-with-email-address-feature-in-wordpress/

Does this make sense? I'm sure the first part of this will work, since I heavily modified this, and used it in a production system. I'm not positive about the second part, but it looks good to me with the filter removal.

Let me know if you want to move onto the paid help session, but this should get you close to where you need to be. Focus on really understanding how those portions work which I said to modify, and you should be able to do it.

@mronikoyi
Copy link

@brian-stinar, i'm looking for the same thing as @poyo12 .
I had someone help me but they simply added it on the register.php from a plugin (i'm afraid that this method will not work if I change that particular plugin to manage my buddypress website).
I was wondering if there was a way to make a standalone plugin that does the same (basically, whenever a person register, a username is generated, either by incrementation from a chosen number, or by random 12 digits generation)
I'm a bit stuck.

@brian-stinar
Copy link

@mronikoyi yes, the cleanest way to do this is with it's OWN plugin. The next thing that would work is to put it in your theme. It's not a good idea to put this in someone else's plugin, since then you'll have the same problems you're describing.

Here's the free help version to get you started:
https://developer.wordpress.org/plugins/intro/

and I recommend using boilerplate plugin code to get started:
https://wppb.me/
https://github.com/DevinVinson/WordPress-Plugin-Boilerplate

Send us a contact request through my consulting company if you want the non-free version of help. These (and the above tested, working code) should be everything you need to get this going.

@mronikoyi
Copy link

thanks @brian-stinar !
I thought so too!

@mronikoyi
Copy link

I didn't knbow about the boilerplate plugin generator! Thanks for the info !

@brian-stinar
Copy link

Sure, no problem. We make a good chunk of cash off open source software, and I try and contribute how I can.

This would actually be a good plugin to write... Maybe I'll have one of my junior level people take care of this.

@mharis
Copy link

mharis commented Sep 23, 2022

Added comment block.

/**
 * Generates a unique username.
 *
 * @param string $username Username to check.
 * @return string username
 */
function generate_unique_username( $username ) {
	$username = sanitize_user( $username );

	static $i;
	if ( null === $i ) {
		$i = 1;
	} else {
		$i ++;
	}
	if ( ! username_exists( $username ) ) {
		return $username;
	}
	$new_username = sprintf( '%s-%s', $username, $i );
	if ( ! username_exists( $new_username ) ) {
		return $new_username;
	} else {
		return call_user_func( __FUNCTION__, $username );
	}
}

@TinkerPaul
Copy link

TinkerPaul commented Oct 15, 2022

New here on GitHub but I thought I'd share my take on this code. My website does not allow user registration. Users are currently created manually. This will allow for use of a csv file (via plugin) to load new users and ensure unique usernames. I replaced the recursive function calls with a while loop. Because of where I hooked into sanitize_user is already performed by the .../includes/user.php edit_user function. Because of hook location removal of the registration error is required.

function generate_unique_username( $errors, $update, $user ) {
        $username = $user->user_login;
        //strip off any trailing digits on the username (Optional, delete if functionality not desired)
        static $loop = 0;
        do {
                $loop++;
                $last = substr($username, strlen($username)-$loop);
        } while ( is_numeric( $last ) );
        $username = substr($username, 0, strlen($username)-($loop-1));

        //iterate to find the next unused username
        $i = 0;
        $new_username = $username;
        while ( username_exists( $new_username ) ) {
                $i ++;
                $new_username = sprintf( '%s%d', $username, $i );
        }

        //Store the new username and clear the error log for username error only
        $user->user_login = $new_username;
        if ($errors->has_errors()) {
                if (in_array('user_login', $errors->get_error_codes()) ) {
                        if(count($errors->get_error_messages( 'user_login' )) == 1) {
                                if(str_contains($errors->get_error_message( 'user_login' ), "This username is already registered")) {
                                        $errors->remove('user_login');
                                }
                        }
                }
        }
}

add_action( 'user_profile_update_errors' , 'generate_unique_username', 10 , 3 );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment