In some of our plugins, we have added multi-site support. For the most part, things are working fine but I'm feeling a bit confused about the correct way to set the currentSite in the Control Panel.

On the front-end, I would expect currentSite could be determined from the Base URL matching that of the request.

On the backend, since all admin behavior is managed from the same base domain, there is nothing specific about the request that would indicate the request is for a certain site so using something like Craft::$app->getSites()->currentSite seems to always give back the same answer.

Does Craft define the currentSite on backend requests? What role should a plugin take in defining the currentSite? Should we go as far as creating the variable for current site itself or could we just set the current site using something like Craft::$app->getSites()->setCurrentSite() and expect Craft to do several things for us? Should we expect different behavior of the currentSite variable when working with Elements vs other components?

Understandably, these are some broad questions. I'm mostly just looking for a better framework to understand what assumptions Craft makes about multi-site on the CP side of things and how we can best plan our code to take advantage of what is already there instead of reinventing the wheel.

1 Answer 1


Maybe this is some kind of broad answer too but when you take a look at your CP requests you'll see: the siteHandle is mostly passed as a variable in nearly every request.

As for the URL routes the last segment is always the siteHandle: By default, the current site will be used in every query but as soon as Craft recognizes a bodyParam siteId/criteria['siteId'] (for example in your baseElementIndex) or in the URL route it uses that site for all ElementQueries.

In your CP there is no such a thing like currentSite (at least it is not really used for important logic) instead the current site is determined like

{% for siteId in groupSiteIds %}
    {% if siteId == entry.siteId %} 
        // currentSite = entry.siteId
    {% else %}
        // not the current site
    {% endif %}
{% endif %}

When I started I used the same method to check for a parameter siteId within the bodyParam criteria. My plugin routes are always something like this

$event->rules[] = [
    'pattern' => 'form-builder/elements/<action:>/<id:\d+>/<siteHandle:\w+>',
    'route' => 'form-builder/element/<action>',
    'defaults' => [
        'id' => null,
        'siteHandle' => Craft::$app->getSites()->getPrimarySite()->handle,
        'action'    => 'index'

if no siteHandle is passed I use the Craft::$app->getSites()->getPrimarySite()


public function actionEditEntry(string $sectionHandle, int $entryId = null, int $draftId = null, int $versionId = null, string $siteHandle = null, Entry $entry = null): Response
if ($siteHandle !== null) {
    $variables['site'] = Craft::$app->getSites()->getSiteByHandle($siteHandle);

    if (!$variables['site']) {
        throw new NotFoundHttpException('Invalid site handle: '.$siteHandle);

later on, when there is really nothing at all it just assumes the following

if (empty($variables['site'])) {
    /** @noinspection PhpUnhandledExceptionInspection */
    $variables['site'] = Craft::$app->getSites()->getCurrentSite();

    if (!in_array($variables['site']->id, $variables['siteIds'], false)) {
        $variables['site'] = Craft::$app->getSites()->getSiteById($variables['siteIds'][0]);

    $site = $variables['site'];


Craft doesn't use the currentSite that much in the CP, it is determined by the current element.siteId or the siteMenu (in js). As soon as you enter the CP the selected site in the menu will be stored in your Javascript session, when you click on create new entry you'll get routed to the site that is specified in your session. I think it's best to manage it the same way

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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