This is a well trodden subject but in Magento 2 I hit a stumbling block (discussed later) so documenting it seemed the right thing to do :)

So what do we want to achieve?

We want to be able to go to a custom url like[frontName]/

[frontName] for purposes of this post is going to be “custom”; If you try going to the above URL it will currently 404 and with good reason. You currently have nothing to tell Magento this is a valid url.

Firstly I presume you have a module registered with Magento. If not, do that now if you want to follow along.

Don’t forget, I use the placeholders [Vendor] and [ModuleName] that you will need to replace with your own details.

We need to tell Magento about our new custom route.

In app/code/[codePool]/[Vendor]/[ModuleName]/etc/frontend/routes.xml:

<?xml version="1.0"?>
<config xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="custom" frontName="custom">
            <module name="[Vendor]_[ModuleName]" />

We do this in the frontend namespace because we only want our new route to be available on the frontend. The id=”custom” can be anything just as long as it is unique. The frontName can also be anything you like but it will define the first bit of the url after the domain name. In this case /custom.

To handle our new route we need to define a new controller.

If you are not familiar with how Magento’s routing works, at a very basic level your URL is made up of three parts.[frontName]/[actionPath]/[actionName]

This translates to the file structure: app/code/[codePool]/[Vendor]/[ModuleName]/Controller/[actionPath]/[actionName].php

[actionPath] and [actionName] by default are both “index” so going to the url is really

Therefore, with that in mind, in app/code/[codePool]/[Vendor]/[ModuleName]/Controller/Index/Index.php:


namespace [Vendor]\[ModuleName]\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\Controller\ResultFactory;

class Index extends Action
    public function execute()
        $page = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
        return $page;

If you now visit the URL it will no longer 404. Cool. That is because it is now routing through your new controller you just created. You will have noticed however that although it no longer 404s, it does now only show a blank page.


Now this is where I was stumped for a short while and eventually had to dig through the Magento code to understand why this was the case.

My first clue was when I viewed the source and found that the class page-layout-admin-1column was being added to the body.

Hang on, why was it adding a class that one would expect to only appear on admin pages?

Once I went deep in to the code I found that if no layout is specified for a route, it defaults to admin-1column. Now that seems strange to me and I don’t fully understand why it falls back to this but it is what it is so let’s sort it out.

We do this with a layout file.

In app/code/[codePool]/[Vendor]/[ModuleName]/view/frontend/layout/custom_index_index.xml:

<?xml version="1.0"?>
<page xmlns:xsi="" layout="3columns" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">

Note that the layout file maps to [frontName][actionPath][actionName].xml

The key bit in the layout file is the layout=”3columns” bit. This declares what layout you wish to use for this particular handle.

Now refresh your web browser and you should see a working website, albeit without any content but at least it is no longer blank.


  • magento2

Like this post? Share it :)

Related Posts