Stefan Ledin

Creating custom routes in WordPress

Sometimes I can really miss the ability to create routes, ”MVC style”, in WordPress. If you have ever worked with Laravel, Ruby on Rails, Express or something like that, you know what I mean.
When I was searching for a good solution to accomplish this, I found an excellent plugin called WP Router.

Route::get('login', function() {
    // Perform some logic and redirect the user somewhere else.
});

That is how you create a route in Laravel. Pretty neat, don’t you think? I think it is.
But the closest thing you can do, at least out of the box, with WordPress is to create a page called ”Login” and a template called page-login.php There you’ll write the logic required to let a user log in.

This might work for you, but I think it feels a bit dirty to create a bunch of empty pages like that. Because of this, I started searching for a solution that was at least a little more like Laravel. As I said, I found WP Router which almost allows me to do this the way I want to.

Why?

You still might be wondering why I would create custom routes. A pretty common scenario is that you want the user to log in with Facebook on your site. There is a number of things that needs to be done to make this happen.
First, the user must click on a link and log in to Facebook.
Then, he or she are redirected back to your site. Then you must do a post request to receive an access token.
Then you can do an API call and get information about the user and do something else based on that.
All of this requires you to write a bit of logic to make it happen. I don’t wanna place all that code along with my markup. I wanna put it in a route!

How?

So how do we create such a route with WP Router? After the plugin has been installed, go to functions.php and paste the following code:

function create_routes( $router ) {

}
add_action( 'wp_router_generate_routes', 'create_routes' );

Inside this function is where we’re gonna register our routes. So let’s create a /login route by placing the following code into the previous function:

$router->add_route('facebook-login', array(
    'path' => 'login',
    'access_callback' => true,      
    'page_callback' => 'facebook_login'
));

The first argument of the add_route method is just the unique id of the route. The second argument is an array of properties for the route. The whole list of options are listed here.
path, access_callback and page_callback are the options that are required to make it work the way I want to. path is set to login, so the URL will be example.com/login.
access_callback is set to true, which just means that a callback function should be called when the user hits the route.
page_callback is set to facebook_login, which is the name of the function that should be called.
So this is how our code looks like this now:

function create_routes( $router ) {
    $router->add_route('facebook-login', array(
        'path' => 'login',
        'access_callback' => true,
        'page_callback' => 'facebook_login'
    ));
}
add_action( 'wp_router_generate_routes', 'create_routes' );

function facebook_login() {

}

You can do whatever it is that you wanna do inside the facebook_login function. In this case, I would generate an access token and do an API call.
With that done, I would redirect the user somewhere else. The destination might depend on the information I’ll receive from the API. If something goes wrong, I might redirect the user to an error page for example.
This is how a simplified example could look like:

function facebook_login() {
    if ($facebook_user) {
        wp_redirect( home_url().'/user' );
    } else {
        wp_redirect( home_url().'/error' );
    }
    exit();
}

I could also load different templates.

function facebook_login() {
    if ($facebook_user) {
        load_template(get_template_directory() . '/user.php', false );
    } else {
        load_template(get_template_directory() . '/error.php', false );
    }
    exit();
}

But what about exit()? Well, it seams like it must be there. Unless you call that function, index.php will be loaded and not the specified route.

More routes

As a final example, this is how the full source code with a couple more routes looks like:

function create_routes( $router ) {
    $router->add_route('facebook-login', array(
        'path' => 'login',
        'access_callback' => true,
        'page_callback' => 'facebook_login'
    ));
    $router->add_route('facebook-logout', array(
        'path' => 'logout',
        'access_callback' => true,
        'page_callback' => 'facebook_logout'
    ));
    $router->add_route('error', array(
        'path' => 'error',
        'access_callback' => true,
        'page_callback' => 'error'
    ));
}
add_action( 'wp_router_generate_routes', 'create_routes' );

function facebook_login() {
    if ($facebook_user) {
        load_template(get_template_directory() . '/user.php', false );
    } else {
        load_template(get_template_directory() . '/error.php', false );
    }
    exit();
}

function facebook_logout() {
    exit();
}

function error() {
    exit();
}

That’s a basic example of possible usage of WP Router. If you check the documentation, you’ll see that there’s more you can do with the plugin. I might write another post about that in the future.

But until then, if you find yourself creating a bunch of routes with lots of logic inside the callback functions, you might reconsider the choice of WordPress as a platform.
Even though WordPress can be used for the most things, it’s not a real MVC framework like Laravel.