Template Routes for Single Entry / Permalink URLs

By default ExpressionEngine’s URLs work like this https://example.com/blog/entry/url-title, where “blog” is a template group, and “entry” is a template, which is fine. But nicer would be something like https://example.com/blog/url-title, cause who needs that extra entry segment anyway? You could handle all of this in the blog template group’s index, but that’s inelegant, full of conditionals, hard to read, and hard to maintain.

Template Routes give us an easy to create and maintain implementation. What you’ll need is the following. A blog template group with the templates index and single-entry.

Inside of index you’ll put your blog listing, this will cover paginated archives, and categorized listings as well—URLs like https://example.com/blog/P15 and https://example.com/blog/category/cat-url-title.

Index might look something like this:

{exp:channel:entries channel='blog' limit='25'}
    <div class="blog-entry">
        <h2><a href="{route='blog/single-entry' url_title='{url_title}'}">{title}</a></h2>
        <p>{entry_date format='%n/%j/%Y'}</p>
        {if has_categories}
            <p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
        {/if}
        <p>by: {author}</p>
    </div>

    {if no_results}
        {redirect='404'}
    {/if}
{/exp:channel:entries}

See that route= variable above? It’ll come into play in a moment. Next you’ll need a single-entry template that will cover your, well, single entries. And will be reached with URLs like https://example.com/blog/url-title.

Single Entry will look something like this:

{exp:channel:entries channel='blog' limit='1' require_entry='yes'}
    <h1>{title}</h1>
    <p>{entry_date format='%n/%j/%Y'}</p>
    {if has_categories}
        <p><b>in</b>: {categories backspace='2'}<a href="{path='blog'}" title="View more in {category_name}">{category_name}</a>, {/categories}</p>
    {/if}
    <p>by: {author}</p>

    {blog_content}

    {if no_results}
        {redirect='404'}
    {/if}
{/exp:channel:entries}

Now that you have those set up, you need to add your route. Go to Developer > Template Manager > Template Routes and set up a route for the template single-entry. Give it a Route of /blog/{url_title:regex[(((?!(P\d+|category\/)).)+?)]} select “no” for “Segments Required?” then save. The regex may look a little complex, so let’s break it down. (((?!(P\d+|category\/)).)+?) uses a negative lookahead assertion, so the route does not match if the second URL segment is either pagination (P\d+) or category/. We want those URL patterns to still use the ExpressionEngine default routing to the blog/index template.

Now in your templates replace any path variables to your single-entry URLs to use the route= variable in the first code sample. This will make sure that when clicked the visitor is sent to the right place!

That’s it! Now go forth and clean up your ExpressionEngine URLs today!

James Mathias's avatar
James Mathias

Hopelessly in love and devoted to his wife of twenty-two years, their three sons, & daughter. For the last eighteen years James has dedicated himself to helping make the Internet better. Previously he…

Comments 4

January 31, 2020

Nelly

Excellent article, template routes are really easy to use!

December 1, 2021

Zignature

Excellent article. It works like a charm for me.

But now I’d like to add the category group (category_url_title) to the url e.g. https://example.com/blog/category_url_title/url_title or https://example.com/blog/category/category_url_title/url_title, preferably the former.

I tried this: Template Route: /blog/&#123;category_url_title&#125;/&#123;url_title:regex[(((?!(P\d+)).)+?)]&#125; single-entry href: &#123;route='blog/single-entry' category_url_title='&#123;categories&#125;&#123;category_url_title&#125;&#123;/categories&#125;' url_title='&#123;url_title&#125;'&#125;

and: Template Route: /blog/category/&#123;category_url_title&#125;/&#123;url_title:regex[(((?!(P\d+)).)+?)]&#125; single-entry href: &#123;route='blog/single-entry' category_url_title='&#123;categories&#125;&#123;category_url_title&#125;&#123;/categories&#125;' url_title='&#123;url_title&#125;'&#125;

Neither worked :( What am I doing wrong?

December 1, 2021

Zignature

the single-entry hrefs I mentioned in my previous comment aren’t in the single-entry file but in the index file of course 😊

December 1, 2021

Zignature

Got it fixed. Added category_group, category and url_title to the exp:channel:entries tag in the single-entry template.