I have setup a Craft 3 multi-site with the following configuration:

enter image description here

But what I really want is:

enter image description here

Additionally, I want to have a 'default' http://localhost:8888 so I can always redirect to /en or /es based on the browser language, but I don't want to add it as a site in the CMS through the admin panel, because I end up with a third unwanted version for each entry.

Which is the way to go?

  • Going with your 2nd image, you could just drop an .htaccess or index.php into whatever folder http://localhost:8888 is configured to serve with your redirect logic in it, no?
    – Brad Bell
    Commented Feb 5, 2018 at 20:08
  • @BradBell I have already unsuccessfully tried that but I may be doing something I shouldn't. My goal is to map my current mysite.com and all it's sections into mysite.com/en and mysite.com/es. Just like microsoft.com is doing; you can never browse microsoft.com/section but instead you get redirected to microsoft.com/en-US/section. I got this to work only when I create a third Craft site: - default (.htaccess and index.php are under public/) - English (.htaccess and index.php are under public/en) - Spanish (they are under public/es) Commented Feb 5, 2018 at 21:08
  • I want to be able to request mySite.com => Then I check craft.request and, based on browser language and other things, decide where I redirect. (I have this part working) Commented Feb 5, 2018 at 21:09

1 Answer 1


This can be done in 3 main steps.

1. Get the site subdirectories working

First just get the /en/ and /es/ subdirectory working:

  • Create en/ and es/ subfolders in your webroot
  • Each subfolder should have its own index.php file and .htaccess file (if using Apache).
  • Assuming you used the craftcms/craft project as a starting point, update the define('CRAFT_BASE_PATH'... line in each of the index.php files to:

    define('CRAFT_BASE_PATH', dirname(__DIR__, 2));
  • Update your English site’s Base URL to include the /en/.

At this point, if you point your browser directly to /en/, the English site should work as expected.

2. Create a redirect script in the webroot’s index.php

Create an index.php file in your webroot (alongside the en/ and es/ subfolders). This script will be responsible for redirecting the request to the correct subfolder, based on the requested URI and language.


// Bootstrap Craft -------------------------------------------------------------

// Set path constants
define('CRAFT_BASE_PATH', dirname(__DIR__));

// Load Composer's autoloader
require_once CRAFT_VENDOR_PATH.'/autoload.php';

// Load dotenv?
if (file_exists(CRAFT_BASE_PATH.'/.env')) {
    (new Dotenv\Dotenv(CRAFT_BASE_PATH))->load();

// Load Craft (but don't run)
define('CRAFT_ENVIRONMENT', getenv('ENVIRONMENT') ?: 'production');
/** @var \craft\web\Application $app */
$app = require CRAFT_VENDOR_PATH.'/craftcms/cms/bootstrap/web.php';

// Custom Logic ----------------------------------------------------------------

// find a site language that matches the browser's requested language
$siteLanguages = $app->i18n->getSiteLocaleIds();
$language = $app->request->getPreferredLanguage($siteLanguages);

// find the first site that uses that language
foreach ($app->sites->getAllSites() as $site) {
    if ($site->language === $language) {
        // found it

// figure out where to redirect this request now
$url = (function() use ($app, $site) {
    $uri = $app->request->getPathInfo();

    // see if we have an exact URI/language match
    if ($element = $app->elements->getElementByUri($uri, $site->id, true)) {
        return $element->getUrl();

    // see if we have an exact URI match in any other site
    foreach ($app->sites->getAllSites() as $otherSite) {
        if ($otherSite !== $site) {
            if ($otherSiteElement = $app->elements->getElementByUri($uri, $otherSite->id, true)) {
                // found one! now see if the element is also available in the requested site
                if ($element = $otherSiteElement::find()->id($otherSiteElement->id)->siteId($site->id)->one()) {
                    return $element->getUrl();
                // otherwise we'll just ignore the browser language
                return $otherSiteElement->getUrl();

    // no element matches. we'll just redirect to the same URI in the requested site.
    return \craft\helpers\UrlHelper::siteUrl($uri, null, null, $site->id);

// redirect to the URL

With that in place, you should be able to start testing the redirects by pointing your browser to /index.php?p=some/uri.

3. Redirect nonlocalized requests to the webroot’s index.php

Finally, we want to get all requests that don’t start with en/ or es/ to be routed to an index.php file in your web root. If you’re using Apache, you can do that with mod_rewrite:

# Send requests missing en/ or es/ to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(en|es) [NC]
RewriteRule (.+) index.php?p=$1 [QSA,L]

Now everything should work!

  • Hi, I've tried this and get an error message: Fatal error: Uncaught TypeError: Argument 1 passed to Dotenv\Dotenv::__construct() must be an instance of Dotenv\Loader, string given. On line 14. Do you know why that could occur?
    – outline4
    Commented Mar 1, 2021 at 21:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.