Most of the routes of your website/application will be defined into the resources/routes.php
file of either your themosis-theme
theme or custom plugin.
For the following documentation, code samples assume we're working with the theme
routes.php
file but all samples work as well inside any pluginsroutes.php
file.
The route system allows you to define WordPress routes as well as custom ones. It extends the Illuminate/Router package providing extra features like group, controller namespace, middlewares and more.
The router has a method for each HTTP verb:
Route::get($uri, $callback)
Route::post($uri, $callback)
Route::put($uri, $callback)
Route::patch($uri, $callback)
Route::delete($uri, $callback)
Route::options($uri, $callback)
Usually WordPress is listening to all HTTP verbs. In order to match WordPress default behavior, you could use the any()
router method like so:
// Listens to HEAD, GET, POST, PUT, PATCH,... HTTP verbs
Route::any($uri, $callback);
If you're using the WordPress Customizer, you might want to use the any()
method because the Customizer is fetching your pages through a HTTP POST request. So if you're using only the Route::get()
method for your theme, you get a blank screen when working with the Customizer.
Finally, there is also the Route::match()
method that lets you define which HTTP verbs you want to listen to. We do recommend its use for general WordPress templating against the any()
method. Here is an example:
// Listens to HEAD, GET and POST HTTP verbs.
Route::match(['GET', 'POST'], $uri, function () {
return 'Hello World!';
});
Pass an array of HTTP verbs as the first parameter, followed by an URI (or condition) and a callback.
A theme is not the only place where you can define routes. You can easily register routes from a custom plugin as well.
The router will use plugin routes as "fallback". Meaning that you can define some routes in a plugin but if the theme registers the same route, it's the route from the theme that will be used. This allows theme developers to overwrite plugin routes and customize the output.
Here is an example. Imagine we have a custom plugin with a registered route that listens to requests made to a custom post type archive of books
:
use Themosis\Facades\Route;
// Plugin routes.php file
Route::match(['get', 'post'], 'postTypeArchive', ['books', function () {
return 'Books from the plugin.';
}]);
Now the theme developer wants to customize the output for the books page, he can overwrite the plugin route like so from the theme routes.php
file:
Route::match(['get', 'post'], 'postTypeArchive', ['books', function () {
return 'Books from the theme.';
}]);
When visiting the books page in the browser, the page is only showing "Books from the theme.".
So keep in mind that a theme routes.php
file has precedence over any plugin routes.php
files.
Now let's dive into WordPress available routes.
The WordPress routes are based on the WordPress template conditional tags. It doesn't work by providing a specific URI but a condition string. Below is a list of "conditions" terms the framework provides based on WordPress core functions. For example, the condition home
refers to the core is_home()
function, the page
condition to the is_page()
function and so on.
Here is the current list of available route conditional tags in alphabetical order (bold = extra conditional tags):
Before digging into the routing API, remember that when you're developing with the Themosis framework, you're still doing WordPress development. So in WordPress, in order for a route to work, the data has to exists first (usually by posting content from the WordPress administration).
Please note that routes are read from top to bottom. It may happen that 2 different routes listen to the same URI. For example: a
postTypeArchive
and anarchive
routes. The router is always returning the first match. As a best practice, we recommend you to put specific routes (in this case, thepostTypeArchive
) on top of yourroutes.php
file and generic routes at the bottom or below (in this case thearchive
route).
In the WordPress world, there are 2 routes available for the home page of your website/application:
home
routefront
routeThis route is mapped to core WordPress function is_home()
. Historically we could say it handled the home page, now it's the blog main page. The home
route handles the request to the URI that lists the most recent published posts.
So there are 2 cases:
/
home
route won't handle requests made to /
URI but perhaps to a /blog
URI instead. In order for this scenario to work, you must define a custom page from the WordPress administration -> Settings -> Reading panel.Here's an example of a route listening to the WordPress Home page:
// This route listens to a '/' or '/custom-page' URI
Route::get('home', function() {
return 'Hello World!';
});
The previous example listens to
GET
andHEAD
requests on the WordPress home page. But you can listen to other HTTP verbs as recommended earlier.
Here is an example of a POST
route:
Route::post('home', function() {
return 'Hello World!';
});
The front
route listens to requests made on your application home page only if you define a front page in the WordPress administration -> Settings -> Reading panel. Once a custom page is defined, this route condition listens to the /
URI:
Route::get('front', function() {
return 'Hello World!';
});
Once the front page is defined, the previous
home
condition is no longer used to listen to the/
URI.
The page
route condition is listening to requests made to any WordPress page posts. Meaning that in order to work, as previously explained, some data has to be registered first from the WordPress administration.
The page
condition is mapped to the core function is_page()
. So by default, the page
route without parameters is triggered for all existing WordPress pages URI. Here is an example:
Route::get('page', function() {
return 'Hello World!';
});
The above code may match requests done to /about
, /contact
, /some-page
, /parent/child-page
, ... URIs.
The
page
route without parameters is a generic route and it is best to register it at the bottom of yourroutes.php
file.
Imagine we have a registered WordPress page with a title of Contact
. WordPress saves the page post with a post name of contact
and makes it accessible at the /contact
URI based on its default permalink structure.
In order to listen to requests made on this contact page, you will write this route:
Route::get('page', ['contact', function() {
return 'Hello World!';
}]);
We recommend you to write specific page routes above the generic page one.
As it follows the is_page()
function, you can pass a string (page title, post name or slug), a page ID or an array as the first parameter of the callback array.
If you want to return same content to multiple specific pages, simply pass an array of pages to the first parameter of the callback array like so:
Route::get('page', [['about', 'contact', 24, 'Our Team'], function() {
return 'Hello World!';
}]);
In order to listen to requests against a single WordPress post, use the single
route condition like so:
Route::get('single', function () {
return 'Hello World!';
});
The above sample will work against all single post. If you want to return a different content for a specific post, you can add arguments to the first parameter of the callback array like so:
// Depending of your permalink structure, this route might listens to
// a /2016/09/23/welcome-post URI.
Route::get('single', ['welcome-post', function () {
return 'Hello World!';
}]);
The above route is only listening to request made to the single post with a slug of welcome-post
. You can also pass a post ID, title or an array.
In order to listen to requests against post archives (paginated), use the archive
condition like so:
Route::get('archive', function () {
return 'Paginated posts.';
});
The
archive
route is generic and might handle the display of custom post type archives as well if none defined and post typehas_archive
property set totrue
.
Core category
taxonomy routes can be listened to by using the category
route condition like so:
Route::get('category', function () {
return 'Category term posts';
});
The above code is listening to requests made to all category terms.
If you want to return a different content for a specific category term, simply pass an argument to the first parameter of the callback array like so:
// Listens to URI like /category/tutorials
Route::get('category', ['tutorial', function () {
return 'Tutorial category posts';
}]);
You can pass the same arguments as found in the core function is_category().
Core 'tag' taxonomy routes can be listened to by using the tag
route condition like so:
Route::get('tag', function () {
return 'Tag term posts';
});
The default tag
route condition is listening to requests made to all tag terms.
If you want to return a different content for a specific category term, simply pass an argument to the first parameter of the callback array like so:
// Listens to URI like /tag/framework
Route::get('tag', ['framework', function () {
return 'Tag framework posts';
}]);
You can pass the same arguments as found in the core function is_tag().
If you want to listen to requests done on a custom taxonomy, simply use the tax
route condition and pass it your custom taxonomy name (slug) like so:
Route::get('tax', ['taxonomy-name', function () {
return 'Hello World!';
}]);
A fix is coming in order to specify a term parameter as well. Stay tuned!
You can route to your WordPress templates on pages and, since WordPress 4.7, on custom post types as well.
Use the template
route condition and specify your template key name like so:
Route::get('template', ['my-custom-template', function () {
return 'Hello World!';
}]);
The template key string is the slug of your registered template from the resources/config/templates.config.php
file stored in your themosis-theme
or custom plugin if specified.
Please note that since release 1.3.0 of the Themosis framework, we use WordPress template system. If you migrate from a version anterior to 1.3.0, please update your
_postmeta
database table and replace all records withmeta_key
equivalent to_themosisPageTemplate
to_wp_page_template
.
In order to register a route that listens to your custom post type single posts, use the singular
route condition and pass it the custom post type name (slug) like so:
Route::get('singular', ['my-custom-post-type', function () {
return 'Hello World!';
}]);
The route is based on the core function is_singular(). Meaning that one singular route can listen to requests made on multiple custom post types posts.
If your custom post type is defined as public => true
and has_archive => true
, the postTypeArchive
condition will listen to requests made on custom post type archive URI. Simply specify your custom post type name like so:
Route::get('postTypeArchive', ['my-custom-post-type', function () {
return 'Hello World!';
}]);
By default, the router handles most of the WordPress core conditional tags functions.
But for some cases, you'll need more control and be able to listen to additional conditions. Conditions from a plugin like woocommerce
and their conditional tags like is_shop
, is_product
or conditions you'll want to create for a specific route.
In order to define a custom route, you'll need to use the themosisRouteConditions
filter like so:
// A custom condition function.
function is_test () {
// Some logic that returns true or false.
// This sample isn't correct as there is no defined logic
// here. In order to work, you need to work this out with
// the WordPress Rewrite API and use the add_rewrite_tag()
// and add_rewrite_rule() functions in order to create a custom
// route condition / endpoint.
return true;
}
// Then add the conditional function to the router.
add_filter('themosisRouteConditions', function ($conds) {
$conds['test'] = 'is_test';
return $conds; // Always return an associative array.
});
The code snippet above is adding the test
route condition which will trigger the is_test()
conditional function.
You can now use this test
route condition like so:
// In routes.php
Route::get('test', function () {
return "Hello from the test route.";
});
When using the
themosisRouteConditions
filter, make sure to return an associative array where the key is the route name and its value is the conditional function signature. Read more about the WordPress Rewrite API.
Define a route that checks for a single product page.
// File store in /admin/routing.php
// Keeping all our route definitions in one place.
// Add the product route.
add_filter('themosisRouteConditions', function () {
$conds['product'] = 'is_product';
return $conds;
});
Then you'll write in your routes.php
file:
Route::get('product', function () {
return view('shop.product');
});
Sometimes we do want routing like in any other framework. This could be a single page where we do not want a user to handle its data or custom API endpoints, ...
Since release 1.3.0, the Themosis framework extends the Illuminate/Router package and provides a "traditional" router in addition to the existing WordPress one.
All router methods described previously are avaible (get, post, put, ...). Custom routes work differently than WordPress routes as in this case, you directly specify the URI you want to listen to without the need of generating data before.
Please take note that even if we get more freedom in routing, make sure that your custom routes do not conflict with WordPress routes. It won't break but if WordPress has a route condition available for your URI perhaps it is best to use it. The choice is yours.
Here is an example of custom routes:
Route::get('/sitemap', function () {
return 'Hello World!';
});
Route::get('/api/v2/projects', function () {
return 'List of projects';
});
Route::post('/api/v2/projects/', function () {
return 'Add one project';
});
If you decide to use custom routes, make sure to write them at the top of your routes.php
file so they take precedence over the WordPress ones.
By looking at the browser "Network", sometimes a custom route response header will return a
404 Not Found
. Each time you define custom routes into your project, make sure to flush the permalink rules either by visiting the WordPress administration -> Settings -> Permalinks page or by using the WP-CLI rewrite flush command from your console or terminal. It only needs to be run once before pushing your project.
You can now capture segments of an URI thanks to route parameters. For example, by taking our previous custom route to a fake API, we could retrieve an ID to a project route like so:
Route::get('/api/v2/projects/{id}', function ($id) {
return "Project with ID {$id}";
});
You may define as many route parameters as required by your route:
Route::get('/forms/{id}/input/{name}', function ($form_id, $input_name) {
return "Custom form {$form_id} and its {$input_name} input field instance.";
});
Make sure to sanitize and escape your parameters values before processing any further.
You can indicate a route parameter as optional. Append a ?
to the parameter like so:
Route::get('projects/{id?}', function ($id = 0) {
return 'One or all projects';
});
Route parameters are not available to the WordPress routes. But by default, each route callback (an anonymous function or a controller method) have as arguments the WordPress globals $post
and $query
.
The first parameter is always the global $post
and the second parameter is the current WordPress query instance $query
. Here is an example:
Route::any('page', function ($post, $query) {
return view('pages', [
'page' => $post
]);
});
Read the controllers guide
Made in Belgium