Build a Custom WordPress User Flow — Part 1: Replace the Login Page

Thanks to its nearly endless customizability through plugins and themes, WordPress has come a long way from its roots as a blogging platform, today acting as the backbone for all kinds of web based applications from online stores to membership sites and e-book authoring platforms.

While traditionally only the site admins would log in to the WordPress dashboard, many of these new, application-like uses change that: when users need to be authenticated, the WordPress login is extended to visitors and customers. 

WordPress provides good tools for managing user roles and capabilities, and they can be further extended with the help of  plugins, but for a professional touch, that's not quite enough — you also need to make sure the user experience fits that of your product as a whole. After all, seeing the default (or lightly styled) WordPress login screen when logging in to a web based application can leave an unfinished impression.

In this three-part tutorial series, I will show you how to fix this by customizing the login and new user registration functionality to fit your site's looks, without violating WordPress's best practices.

What You Will Learn in This Tutorial

By default, WordPress uses a good looking, but generic login screen that we are all very familiar with:

WordPress login screen

The screen does it job, and it doesn't look bad at all. But when building a serious web site, you don't keep the default theme, so why would you keep the default login?

It is possible to do some styling using CSS and a number of actions and filters provided by the login page itself. But while these changes can lead to decent results, I think the approach still has its shortcomings, most importantly the fact that the login page is totally separate from the rest of your site.

That's why, today, we are not going to settle with the simple, cosmetic changes but will instead build a login page of our own. One that feels like a part of the WordPress based web site rather than something added on top of it.

To do this, in this tutorial, we will build a WordPress plugin that replaces the login page with a combination of a custom WordPress page and a shortcode used for rendering the login form. While doing so, you will learn about the WordPress login flow, the points were we can hook into it and modify it to our needs, as well as developing WordPress plugins. 

Then, in the next two tutorials in the series, we will continue from there, customizing the new user registration and password reset functionality in the same way.

You can follow the tutorial implementing your own plugin as you go, or you can download the full sample code from the linked GitHub repository.

Let’s get started!

Create the Plugin

While some of the customization is clearly dependent on the theme used, the functionality of making the login page customizable itself is very much independent of themes and therefore best implemented as a plugin.

So, the first thing as we get started is to create a plugin to hold all of the customizations we will build in this tutorial.   

First, create a directory to hold the plugin, naming it personalize-login. Then, inside that directory, create another directory and name it templates. In this directory, we will add the templates used for rendering the different HTML forms created in this series (we'll get back to this later in the tutorial).

Then, with the directories created, place a PHP file called personalize-login.php in the plugin directory. This file will contain all the code for this plugin, starting with the plugin initialization:

The plugin's constructor is still empty, but not for long: we'll soon start adding content to it, in the form of filter and action hooks.

But first, let's create some WordPress pages.

Create the Custom Pages For Login

By default, when you try to access the WordPress admin, or click on a Log In link on your WordPress site (assuming your theme displays one), WordPress sends you to wp-login.php, the default version of the WordPress login page. Now, we'll change that and send the user to a custom WordPress page. 

To do this, first we need to create a page.

At a later point, it might make sense to add a settings screen for picking the pages to use for each of the different steps in the user management flow. For now, however, I want to keep your focus on the customization and not confuse you with creating settings pages, so we will go with the simple option and just create the pages automatically when the plugin is activated.

In a WordPress plugin, the way to do this is by registering a static function to a plugin activation hook so that it is called when plugin is activated through the Plugins menu. 

Notice that the activation hook does not get called when a plugin is updated through the plugin updates. So, if you make changes to the code inside the activation hook and want to have them take action, make sure that you deactivate and activate the plugin manually. 

To register the activation hook, add the following piece of code at the end of your plugin file:

Then, inside the plugin class, add the static function plugin_activated:

The plugin activation function creates a set of pages using the data from the $page_definitions array defined on lines 8-17. Using this array, we can easily add more pages (as we'll do in the next parts of the series) without copying and pasting page creation code. All we'll need to do is to add a new item to the array, using the page slug (permalink) as the key and an array containing the page's title and initial content as value. 

In the code snippet above, I added two pages: one for the login page (member-login) and one for an account page where we'll direct the logged in non-admin users (member-account).

On lines 19-36, you'll find the actual page creation code. The function loops through the array of page definitions checking if a page with the given slug exists already and creating a page if it doesn't. 

Creating the pages is done using the WordPress function wp_insert_post. For more information on its parameters, you can check the WordPress Codex.

Now, let's activate the plugin and see that pages are created the way they should. Go to your WordPress admin dashboard and activate the plugin.Then, when you access the Pages menu, you should see both new pages in place:

The Pages list now shows the two pages we just created

If you click over to view the Sign In page, you'll find an almost blank page with nothing but a title and our shortcode. 

But it won't be blank for long.

Add Content to the Login Page

With the first two pages in place, we can start adding content to them.

In this first tutorial, we will build the Log In — or Sign In (see this article on login best practices for a discussion on what words to use) — page. Then, in the next two tutorials, we will continue from there, building the "Register" and "Forgot Your Password" pages.

Having your own page for the login screen gives you many options for customizing its looks and content. On the easier side, you can use the WordPress page editor to add text and graphics (e.g. Instructions for the user, or maybe an advertisement about the benefits of registering an account). At the other extreme, you could even create your own page template and write the HTML for the page from scratch. 

In this tutorial, we will do something from the middle and build a shortcode that will display a login form on the login page. If you later decide to create your own page template, you can always call this shortcode from your theme's code.

Step 1: Create the Shortcode

As you remember from the page creation step, we already added a shortcode (custom-login-form) in the body of the login page when we created it in the plugin activation hook. However, as we haven't yet built a handler for that shortcode, WordPress now renders it as regular text.

Let's create the handler now. In your plugin class's constructor, add the following shortcode definition:

This line registers a function inside the plugin class, render_login_form, for rendering the shortcode custom-login-form

To make this work, add the function:

First, on lines 10-13, the function reads in the shortcode parameters — currently there is just one, show_title, which take a boolean value. If the parameter is omitted, by default, it is set to false.

Then, the shortcode function checks if the user is logged in, rendering the form only for users who are not yet logged in.

On lines 19-25, the function checks if a redirect_to request variable was passed to the login page. This parameter is what gets you to the correct WordPress admin page after login, so it's important to not forget it as we customize the login flow. If no redirect_to is set, or the URL is not valid, the function uses the current page's permalink instead.

To keep presentation separate from functionality, the rendering is done using a template placed in the templates directory you created at the beginning of this tutorial.

As we already know we will be adding more templates later in this series (for example the new user registration form), we will prepare for this and separate the template rendering into its own function to avoid duplicating code (lines 27-28). Variables needed in rendering the template are passed as an associative array, $attributes.

Add the function, get_template_html, to the plugin class:

The function looks for the given template in the templates directory and renders it into a string using an output buffer (lines 14-23). The output buffer collects everything that is printed between ob_start and ob_end_clean so that it can then be retrieved as a string using ob_get_contents

At the end of the function, the shortcode output is returned for rendering on the page (as you saw in the previous snippet, the shortcode function then returns it to WordPress for rendering).

The do_action function calls surrounding the require are optional but show a handy way to give other developers a chance to add further customizations before and after the template is rendered.

To complete the shortcode, you still need to create the HTML template: Create a PHP file in your templates directory, naming it login_form.php. Inside the template, to test, just add the text Sign In. Now, when you navigate to your custom login page, you'll see something like this (using the current WordPress default theme, Twenty Fifteen):

The new Sign In page still empty

Your shortcode is working, but it's still missing the login form. Let's add that now.

Step 2: Add Contents to the HTML Template

Continuing the work on login_form.php, remove the placeholder text and replace it with the following content that will show a fully functional WordPress login form:

First, the template begins with an optional title, controlled by the shortcode parameter, show_title as explained earlier.

Then, on lines 6-14, you'll notice that most of the rendering of the login form is still done by WordPress, using the function wp_login_form. The function takes an optional parameter array that can be used to customize the contents of the login form. 

Here are the customizations I have made in our example code:

  • First, I wanted to use the wording "Sign In" instead of the default "Log In".  
  • Second, I wanted to use use email addresses as user names, so I changed the label for user name to "Email".
  • Third, there is the redirect URL which was forwarded through the shortcode handler function above.

For further customizations, here's the full list of parameters you can use in the parameter array passed to wp_login_form:

Default Value
Whether the login form should be rendered. If false, the output is returned as a string.
The URL to redirect to after successful login. Redirects back to current page.
The HTML id of the login form.
The text label for the user name field.
__( 'Username' )
The text label for the password field.
__( 'Password' )
The text label for the "Remember Me" checkbox.
__( 'Remember Me' )
The text label for the "Log In" button.
__( 'Log In' )
The HTML id for the user name field.
The HTML id for the password field.
The HTML id for the "Remember Me" checkbox.
The HTML id for the submit button. wp-submit
Whether the "Remember Me" checkbox should be shown or not. true
Default value for user name. Empty
Whether the "Remember Me" checkbox should be checked initially or not. false

Finally, on lines 16-18, there is a link to the WordPress lost password functionality — which we will replace with our own version in Part 3 of the tutorial series.

Now, the login page looks like this: 

Custom login form

For most uses, this will be enough customization, but if you want to go deeper, there is nothing stopping you from creating the login form from scratch. 

In this case, you can just create a HTML form and post its content to  wp-login.php — the full login URL for your current site is returned by the WordPress function wp_login_url. The form needs to have the fields log and pwd for the login and password respectively.

Here's how you would create a login form that looks (and functions) just like the one above — without using wp_login_form:

WordPress handles the login functionality, so the login form is already fully functional: Enter your valid user name and password and hit Sign In and you will be logged in just fine.

It's when you enter invalid data that you'll notice everything isn't quite ready yet: Instead of being redirected back to the login page we just created, you will see the regular WordPress login page. Also, unless the user points her browser directly to this newly created login page, she still sees the default WordPress login.

We need to take the new login page to use by redirecting users to it.

Redirect the Visitor to Login Page

Let's start with bringing the user to the login page in the first place.

Step 1: Redirect the User to Our Login Page

When a user tries to access any page inside the WordPress admin, or clicks on a Log in link in the Meta widget, he is sent to wp-login.php. Next, we want to change this functionality and point the user to our own member-login page instead.

All of the WordPress functionality we are customizing in this series from login to password resetting is handled in wp-login.php with the different parts of the functionality identified with a parameter called action

If you look at the code inside the PHP file, you will notice that right before the actual login functionality begins, two actions are fired: login_init and login_form_{action} where {action} is the name of an action being executed (for example login, postpass, or logout).

The second one is more specific, so by using it, we won't have to do all that much checking ourselves: just by hooking a function to login_form_login, we can target our redirect functionality precisely to the moment a user enters the login page.

Add the following action to the plugin class's constructor:

Then, add the action function:

The redirect_to_custom_login function is called in two situations: first, when the user enters the login page, and second, when he or she submits the login form. To differentiate between the two, we look at the request method: the form is submitted using POST while the page is loaded using GET

So, to let wp-login.php handle the actual login and authentication, on line 5, we make sure that the redirect is only done on GET requests.

On line 6, if a request variable redirect_to is passed in, we'll store it in a temporary variable $redirect_to.

Already logged in users don't need to go through the login form, so we'll redirect them to either to the WordPress dashboard or the member-account page created earlier (lines 8-11). To prepare for similar redirects in the rest of the tutorial series, we'll separate the redirect to its own function, redirect_logged_in_user, which we'll add soon.

One thing worth noting is that we are relying on the page slugs (pretty permalinks) in specifying the pages we redirect the user to. If you want to improve this example and make the plugin more flexible, you could look up the pages using the slug and then create permalinks using that data. For now, just turn on pretty permalinks (any option except the default is OK) on the Permalinks settings page and everything will work just fine.

On lines 15-17, we add the redirect_to parameter to the new request if one is present so that no information is lost at this redirect.

On line 20, you'll notice that we end the execution rather abruptly using an exit command. This is important because otherwise, the execution will continue with the rest of the action in wp-login.php, maybe causing problems with our customization. Keep an eye on this also in other redirects we do in the rest of the tutorial.

Now, add the function, redirect_logged_in_user mentioned above:

The function redirects the user to a different URL depending on his or her access level. For admin users, we also check the $redirect_to parameter: if one is set, the user is directed to this URL instead of the default admin_url.

Step 2: Redirect When There Are Errors

We have now covered the first redirect and so, whenever the user first clicks on a link to wp-login.php or tries to access the admin dashboard, he or she is sent to our new login page. But we're not done yet: when there are errors in the login (invalid username or password, empty form, and so on) the user still sees the default login page. 

In WordPress, user authentication happens through a function called wp_authenticate —  located in pluggable.php if you want to take a look. 

The function does some basic sanitization of parameters and then calls the filters attached to the filter hook authenticate. This is to allow plugins to replace the authentication flow with their own functionality, but most importantly for us, even the WordPress default authentication is done through this filter

At this point, we are not interested in replacing the authentication, but the filter hook is still useful as it gives us a chance to collect the errors from other filters and proceed accordingly: 

  1. Hook a new function to the authenticate filter using a high enough priority (in WordPress filters and actions, a higher number in the priority parameter means that the function will be called later in the filter processing order).
  2. The filter function will receive the results from previous filters in the filter chain as a parameter. Use this data to check if there were any errors in the login.
  3. If no errors are found, let everything proceed normally so WordPress can finish the login. 
  4. If there are errors, instead of letting WordPress do its regular error handling, redirect to our custom login page.

In the current WordPress version (4.2 at the time of writing), WordPress has the following two filters hooked to authenticate:

The basic authentication has priority 20 and the priority for wp_authenticate_spam_check is 99, so if we set ours at higher than 99 (I picked 101), it will be run after both of these. It is of course possible that another plugin you use adds a filter with a higher priority, in which case you’ll need to increase the number to take that plugin's errors into account. 

First, add the new filter definition in our plugin class's constructor:

Then, add the function, maybe_redirect_at_authenticate, at the end of the plugin class:

The authenticate filter functions take three parameters: 

  • $user is the signed in user's user object, or an instance of the Wp_Error class if there have been errors in the filter chain so far. 
  • $username is the user name entered by the user trying to log in.
  • $password is the password entered by the user.

For our use, the first parameter is the interesting one: if $user is an error that means one of the earlier authenticate filter functions has found an error in the login. That's when our  function jumps in (on line 13) to collect all of the error codes and append them to the redirect URL on lines 15-18. Finally, the function redirects the user to our custom login page to display the errors.

Step 3: Render Error Messages

The plugin is now catching the errors and redirecting the user back to our custom login page also when it finds some. To make the experience useful for the user, we still need to print out the error messages. 

First, in our shortcode function, we will use the login parameter sent by the redirect created in the previous step to see what errors have occurred and to replace the error codes with readable error messages. This is a good place to use your imagination and create some fun and personal (but hopefully not annoying) error messages. 

Create a function for converting error codes to error messages, using a simple construct. The error codes are the ones used by the WordPress login functionality.

Then, to use the error messages, add the following code to render_login_form, right before rendering the login form template. It will go through the list of error codes and collect matching error messages into an array, $errors:

The actual rendering of the error messages is done in the login_form.php template. Add the following right after the login form's title and before the login form:

Now, the error messages are in place and we can test them. Try signing in using incorrect information and you will be redirected back to our custom page with a custom error message of our own:

Error message on the custom Sign In page

Step 4: Show Correct Message at Logout

We're almost done — but not quite. There are still two more things to do before we can call it for a day and say we have replaced all of the WordPress login flow with our own version: rendering the correct message when a user signs out, and redirecting the user to the correct page after a successful login. 

Let's start with the logout. 

Once the user has been logged out from your site, in the function wp_logout, WordPress fires the action wp_logout. This is a good place to hook to and do our own redirects before WordPress redirects the user back to wp-login.php.

In the plugin class's constructor, add the following line:

Then add the matching function:

The function redirects the user to our member-login page with the parameter logged_out appended to the request and then stops the execution.

Next, modify the login form shortcode function to take this parameter into account. A good place for this code is right after the error message handling we created above:

Finally, we need to print a notification in the template. Again, add this right after printing out potential errors:

That's it. Now when you sign in and then sign out, you'll see something like this:

Sign In page with You have signed out message

Redirect the User to the Account Page When Logged In

We have now implemented the custom login and handled all of the error cases. As a final touch, to make everything feel just right, let's add one last redirect. This one will make sure the user always ends up where he or she wants (and is allowed) to go. 

For admins, this is the admin dashboard and for regular, non-admin users, the (currently empty) member-account page.

As WordPress provides a filter for returning the redirect URL after a successful login, this one is rather straightforward: all we need to do is to create a function that checks the current user's capabilities and returns the correct redirect URL accordingly.

To do this, add one last filter, again in the plugin's constructor:

Then add the matching filter function:

First, the function checks if the user really was logged in (on line 13) and redirects back to the site's home page if no user is found.

Then, it picks the correct redirect depending on the current user's capabilities:

  • Admins (checked on line 17, using the user_can function) are redirected to the WordPress admin dashboard (line 20) unless a redirect_to parameter was passed with the request. 
  • Regular users are always redirected to the member-account page to prevent them from accessing the WordPress dashboard. This is of course up to you: if you want, you can for example point them to their user page in the WordPress admin.

If you like, you can also add more fine grained redirects depending on users' roles and capabilities.

What’s next?

That's it. You have now built a plugin to replace the WordPress login screen with a custom page that you can customize to make it fit your theme, or the product you are building on top of WordPress.

In the next tutorial in this series we will build upon this and create a new user registration flow, adding a few new fields and a captcha to prevent bots from registering. 

Until then, have fun tweaking your login page!



Related Articles