As we already described in the opening Page -> Folders section, routing in Grav is primarily controlled by the folder structure you use when you build your site content.
There are certain scenarios where you need more flexibility and Grav comes packed with a variety of tools and configuration options to make your life simpler in this regard.
Imagine if you moved your site from some other CMS platform to Grav, you have several choices on how to set up your new site:
There are many other use cases where you may wish to have the Grav site respond to different URLs than the folder structure dictates, and Grav has the following capabilities to help you realize your objectives.
As outlined in the Headers -> Routes section, you can provide explicit routing options for the default route as well as an array of route aliases:
routes: default: '/my/example/page' canonical: '/canonical/url/alias' aliases: - '/some/other/route' - '/can-be-any-valid-slug'
These are processed and cached per-page, and are available along with what we call the raw route which is the route based on the slugs of the page hierarchy (which is how Grav works out a route by default). So even if you provide custom page routes, the raw route is still always valid too.
Similar to page level routing, Grav also supports page level redirects by specifying the target page in the page header. See Headers -> Redirect section for more details.
Grav has a powerful regex-based mechanism for handling route aliases and redirects from one page to another. This feature is particularly useful when you migrate a site to Grav and want to ensure the old URLs will still work with the new site. This is often best accomplished via rewrite rules using your web server, but sometimes it's more convenient and flexible just to let Grav handle them.
These are handled via the Site Configuration. Grav comes with a sample
system/config/site.yaml but you can override or add any of your own settings by editing the
All redirect rules apply on the slug-path beginning after the language part (if you use multi-language pages)
You must escape certain characters in any routes that you want to match. This is especially important to know if you are migrating an old site that used links containing legacy file extensions (e.g.
.php) or URL parameters (
?foo=bar). In these examples, the period and question mark must be escaped like
The most basic kind of alias is a direct one-to-one mapping. In the
routes: section of the
site.yaml, you can create a list of mappings to indicate the alias and the actual route that should be used.
It's important to note that these aliases are only used if no valid page is found with the route provided
routes: /something/else: '/blog/focus-and-blur'
If you requested a URL
http://mysite.com/something/else and that was not a valid page, the routes definition would actually serve you the page located at
/blog/focus-and-blur, assuming it exists. This does not actually redirect the user to the provided page, it simply displays the page when you request the alias.
The indentation is key here, without it the route redirect will not work.
A more advanced type of alias redirect allows the use of a simple regex to map part of an alias to a route. For example, if you had:
routes: /another/(.*): '/blog/$1'
This would route the wildcard from the alias to the route, so
http://mysite.com/another/focus-and-blur would actually display the page found at the
/blog/focus-and-blur route. This is a powerful way to map one set of URLs to another. Great for moving your site from WordPress to Grav :)
You can also perform the match to capture any alias, and map that to a specific route:
routes: /one-ring/(.*): '/blog/sunshine-in-the-hills'
With this route alias, any URL that confirms to the wildcard:
/one-ring/is-mine.html will both show the content from the page with the route
You can even get much more creative and map multiple items or use any regex syntax:
routes: /complex/(category|section)/(.*): /blog/$1/folder/$2
This would match and rewrite the following:
/complex/category/article-1 -> /blog/category/folder/article-1 /complex/section/article-2.html -> /blog/section/folder/article-2.html
This route would not match anything that doesn't start with
complex/section. For more information, Regexr.com is a fantastic resource to learn about and test regular expressions.
The other corollary option to route aliases is provided by redirects. These are similar, but rather than keeping the URL and simply serving the content from the aliased route, Grav actually redirects the browser to the mapped page.
There are three system-level configuration options that affect Redirects:
pages: redirect_default_route: false redirect_default_code: 302 redirect_trailing_slash: true
redirect_default_routeenables Grav to automatically redirect to the page's default route.
redirect_default_codeallows you to set the default HTTP redirect codes:
redirect_trailing_slashoption lets you redirect to a non-trailing slash version of the current URL
redirects: /jungle: '/blog/the-urban-jungle'
You can also explicitly pass the redirect code between square brackets
 as part of the URL:
redirects: /jungle: '/blog/the-urban-jungle'
If you were to point your browser to
http://mysite.com/jungle, you would actually get redirected and end up on the page:
The same regular expression capabilities that exist for Route Aliases, also exist for Redirects. For example:
redirects: /redirect-test/(.*): /$1 /complex/(category|section)/(.*): /blog/$1/folder/$2
These look almost identical to the Route Alias version, but instead of transparently showing the new page, Grav actually redirects the browser and loads the new page specifically.
When you set a certain page to be your site's home via the
home: alias: '/home'
You are effectively telling Grav to add a route of
/ as an alias for that page. This means that when Grav is requesting the page for the
/ URL, it finds the page you have set.
However, Grav really doesn't do anything special for pages that are beneath this homepage. So if you have a page called
/blog that displays a list of your blog posts, and you set this to be your homepage, it will work as expected. If however, you click on a blog post that sits beneath the
/blog folder, the URL could be
/blog/my-blog-post. This is expected behavior, but it might not be what you intend. There is a new option available via the
system.yaml that let's you hide this top level
/blog from the route if so enabled.
You can enable this behavior by toggling the following value:
home: hide_in_urls: true
Found errors? Think you can improve this documentation? Simply click the Edit link at the top of the page, and then the icon on Github to make your changes.