Automatically "translate" URLs / Set Default Route to match the Title


Our default language is german, which means that all folders are in german. This will result in “” for example. After we translated the pages to industries (for “Branchen”) and transport systems (for “transportsysteme”), the URL will show up as “”. Is there a way to make this automatically take the page titles and change it to “”?

Thank you!

I would be great if there was a way to set Advanced -> Route Overrides -> Default Route to match the Content -> Title automatically, even when updated. Is there any way to achieve this?

Any ideas on this? :frowning:

Have a look at Multi-Language Routing.

In short:
When you add ‘title’ and ‘slug’ variables to the frontmatter of the English version of page ‘branchen’ and ‘transportsysteme’, these variables will override the default German url and menu values.

For example in folder for ‘Branchen’:

title: Industries
slug: industries

The url will become

As a side note, I see your urls contain the language code ‘de’ and ‘en’. Is that on purpose, or would you like to leave the language code out for the default language? Like versus

If you would like to ommit the default language code see Default Language Prefix

Thank you!

@metbril, that means that I have to write a complete plugin to achieve that, right?

@pamtbaau, my problem is that I want to automate that the page title will always be the custom slug so that our content guys won’t have to enter it twice. I already “skipped” the /de/ as it’s the default language.

Ahh now I understand… I hope…

I know no other solution than the one mentioned by @metbril.

If I understand your needs correctly this time… then the following might be doing what you are after.

You might need some more regex to replace invalid chars like for example 's.

Creating framework of plugin:

$ bin/plugin devtools newplugin

And replace most of old code and add the following:

public function onPluginsInitialized()
    // Don't proceed if we are in the admin plugin
    if (!$this->isAdmin()) {

    // Enable the main event we are interested in
        'onAdminSave' => ['onAdminSave', 0]

public function onAdminSave(Event $e)
    $page = $e['object'];
    $slug = strtolower(preg_replace('/ /', '-', $page->title()));

    $header = $page->header();
    $header->slug = $slug;

@pamtbaau, thank you! That’s nearly the same code I’m currently working on :slight_smile:

As you said I will have to add more replacements. Is there any way to use the native replacement function and patterns that grav uses to generate urls or folder names?

In Admin/Pages I tried to add a new page with name “Qqq’s Www”:

  • In the modal popup, while typing the page’s name, the folder name gets a transformed value “qqqs-www”.
    Seems to me this is done using Javascript.
  • A folder is created named /pages/05.qqqs-www

In Admin/Pages I tried to rename the foldername in tab Advanced to “Qqq’s Www”:

  • The backend throws an error on save with message that value is incorrect.
    The error is thrown in method BlueprintSchema::validate() which will validate the page’s header against rules.
  • No correction occurs.

Considering the above, I’m not sure if you can reuse any transformation code when the user changes the Title.

Related topic in nee thread

@pamtbaau, thanks for checking this out. I wrote my own rule to transform umlauts like “ä” to “ae” and to remove all non-alphanumeric chars. Maybe I will also add a transformation for all other special characters to their closest equivalent, like “ø” to “o”.

In onPluginsInitialized() I had to add a line that checks if the page I’m on is a content page and not somewhere in a plugin or in the configs. I don’t know if this is the best practice but it just checks if the uri path contains “pages”:

if ($this->grav['uri']->paths()[1] == 'pages') {
      'onAdminSave' => ['onAdminSave', 0]

Maybe this answer on Stackoverflow can help to translate umlauts and others.

The extra check in onPluginsInitialized() sounds like a good idea… I think you should also check if Uri::paths() returns 3 elements. When creating a folder, Uri::paths() only returns 2 elements.

NB. Maybe it’s a good idea to mark the thread as solved, so others know you have found a solution.

I will mark it as solved once it’s solved :wink: I just found another issue: When I hit save on a modular and it creates a slug it is no longer modular because the template switches to a page template :frowning: Now I have to figure out how to detect the template


But I cannot reproduce… Modular page remains modular, module remains module…

Can you give me the steps to reproduce?
What are you seeing?

It happens even with my plugin deactivated. When I set a slug for a module in the advanced tab and hit save, the module becomes a page and under page templates only page templates are listed (automatically chooses the first in alphabetic order). When I uncheck the slug and hit save it gets reverted to a module but also choosing the first template alphabetic order.

@Paddi This could well be an ‘undocumented feature’ of Grav…

It is however a bit silly to change the slug of a child of a modular, because the child-page is never displayed on its own and hence doesn’t need a slug to set the url. I guess…

Some tests:

  • If I set a translated slug for the main Module page, all goes well. Page gets a translated url.
  • If I set a translated slug for a child page, things go wrong as you have noticed.
  • If I only translate the title of a child page, the menu gets translated and the subpage urls becomes translated into ‘translated_url#tanslated_title’.
    Note: Spaces are replaced by ‘_’ instead of ‘-’

The following extra check in ‘onAdminSave()’, might help to prevent setting slug for children:

// Do not set slug if page is child of modular eg. 'modular/features'
if (strpos($page->template(), '/') > 0) {

I think this is why this bug is happening because slugs are not intended to be used on modules.

$page->template() was what I was looking for, thank you. I changed it a bit so it is checking if “modular” is in the String and if false, the code will be executed.

Now everything is working, at least I think so :smiley: Maybe I will have some more “surprises” while using this plugin. I think this feature should be implemented in Grav itself. Without this workaround it’s just a matter of time until one of our content creators forget to set the slug for english content and then we have a german url for an english page…