This is the documentation for concrete5 version 5.6 and earlier. View Current Documentation

Originally came interested about the subject during a conversation at the forums but it seems that although the URL structure cannot be configured in concrete5, it is quite easy to add the .htm or .html extension to the URL structure through few customizations.

DISCLAIMER: in most cases you should not be interested in this topic. This is just for those poor people that have to deal with stubborn people. In 99.999999999879% of cases, you should encourage using 301 redirects for the old pages instead.

First things first:

  • This probably isn't a glitch free, thoroughly thought solution, the idea is just to demonstrate this is possible quite easily
  • Using this method, the URLs are still accessible through the old URLs, so if you're concerned about that, you should probably extend the .htaccess rules a bit
  • This is only mildly tested, if you prefer chili on your code, you might need to put some spices on it
  • Some add-ons and custom hacks might have issues with this method

Then to the actual steps to make this work.

Step 1: enable pretty URLs

Write "Pretty URLs" to the intelligent search and enable the option. Cheer: yay concrete5! :)

Step 2: enable URL_REWRITING_ALL option

This is to avoid problem states e.g. with single pages, so add this to your /config/site.php

define('URL_REWRITING_ALL', true);

Step 3: apply the additional rewrite rule

Adding these few lines to your rewrite rules enable you to access the pages normally by passing the page path with the .htm extension. So, edit your .htaccess and add these lines after the "RewriteBase" setting in the default concrete5 rewrite rules:

RewriteBase /    
RewriteCond %{REQUEST_URI} ^(.*).htm$
RewriteRule ^(.*)\.htm$ $1 [L]

Other parts of your .htaccess will remain the same, make sure these lines come BEFORE the other concrete5 rewrite conditions and rules. The RewriteBase setting in the code lines above is there just to demonstrate the correct spot.

Save the file and try to access some of your URLs through the new URL structure, like (should show the same page as used to).

Step 4: modify your link structure

Having the rewrite rule in place won't magically write over the URLs that concrete5 produces. This requires you to do few overrides:

  • The Auto-Nav block controller: /blocks/autonav/controller.php
  • The navigation helper: /helpers/navigation.php
  • The view library: /libraries/view.php

There might be other files to override as well but these override probably handle most of the cases you should be interested in. Also, with these guides, you can pretty easily apply the same customizations elsewhere as well.

Fortunately since 5.6.0, overriding the core files is easy as building a Titanic replica (well, honestly more like eating a strawberry). You only really need to write the parts you really need to override.

So, let's get started with the Auto-Nav block, controller, copy the template from the /concrete/blocks/autonav/controller.php to the root-level and apply this fix:

class AutonavBlockController extends Concrete5_Controller_Block_Autonav { }
class AutonavBlockItem extends Concrete5_Controller_Block_AutonavItem {
    public function getURL() {
        $link = rtrim(parent::getURL(), '/');
        return $link == DIR_REL ? $link : $link . '.htm';

Then, let's go over to the navigation helper, the same old trick /concrete/helpers/navigation.php => /helpers/navigation.php and paste some code:

class NavigationHelper extends Concrete5_Helper_Navigation {
    public function getLinkToCollection(&$cObj, $appendBaseURL = false, $ignoreUrlRewriting = false) {
        $link = rtrim(parent::getLinkToCollection($cObj, $appendBaseURL, $ignoreUrlRewriting), '/');
        return $link == DIR_REL ? $link : $link . '.htm';

All right, one more to beat, again /concrete/libraries/view.php => /libraries/view.php and do some cut'n'paste:

class View extends Concrete5_Library_View {

    public function url($action, $task = null) {
        $url = parent::url($action, $task);
        return rtrim($url, '/') . '.htm';


All right, you're there. Grab a cup of coffee and hope for the best. If some things don't work or require more work, grab a beer instead and post some additional tips here once finished debugging your situation.

Loading Conversation