Skip to content

Powered by Grav + Helios

Multisite Setup

Multisite Setup

Caution

Grav has preliminary multisite support available. However, the Admin plugin still need to be updated to fully support multisite configurations. We will continue to work on this in subsequent releases of Grav.

What is a Multisite Setup?

A multisite setup allows you to create and manage a network of multiple websites, all running on a single installation.

Grav has built-in multisite support. This functionality extends the basic environment configuration, which lets you define custom environments for your production and development sites.

A full multisite setup gives you the power to change how and from where Grav loads all its files.

Requirements for a Grav Multisite Setup

The most important thing you will need to run a Grav multisite network is good website hosting. If you are not planning to create many sites and do not expect many visitors, then you can get away with shared hosting. However, due to the nature of multisites, you’d probably need a VPS or dedicated server as your sites grow.

Setup and installation

Before you begin, you’ll want to be sure your web server is capable of running multiple websites i.e., you have access to your Grav root directory.

This is essential since serving multiple websites from the same installation is based on a setup.php file located in your Grav root.

Quickstart (for Beginners)

Once created, the setup.php is called every time a user requests a page. In order to serve multiple websites from one single installation, this script (roughly speaking) has to tell Grav where the files (for the configurations, themes, plugins, pages etc.) for a specific subsite are located.

The provided snippets below setup your Grav installation in such a way that a request like

TXT
https://<subsite>.example.com   -->   user/env/<subsite>.example.com

or

TXT
https://example.com/<subsite>   -->   user/env/<subsite>

will use the user/env directory as the base "user" path instead of the user directory.

If you choose sub-directories or path based URLs for subsites, then the only thing you need is to create a directory for each subsite in the user/env directory containing at least the required folders config, pages, plugins, and themes.

If you choose sub-domains for structuring your website network, then you will have to configure (wildcard) sub-domains on your server in addition to the setup of your subsites in your user/env directory.

Either way, decide which setup suits you best.

Snippets

For subsites accessible via sub-domains copy the setup_subdomain.php file, otherwise for subsites accessible via sub-directories the setup_subdirectory.php file into your setup.php.

Note

The setup.php file must be put in the Grav root folder, the same folder where you can find index.php, README.md and the other Grav files.

setup_subdomain.php:

PHP
 1<?php
 2/**
 3 * Multisite setup for subsites accessible via sub-domains.
 4 *
 5 * DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING!
 6 */
 7
 8use Grav\Common\Utils;
 9
10// Get subsite name from sub-domain
11$environment = isset($_SERVER['HTTP_HOST'])
12    ? $_SERVER['HTTP_HOST']
13    : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
14// Remove port from HTTP_HOST generated $environment
15$environment = strtolower(Utils::substrToString($environment, ':'));
16$folder = "env/{$environment}";
17
18if ($environment === 'localhost' || !is_dir(ROOT_DIR . "user/{$folder}")) {
19    return [];
20}
21
22return [
23    'environment' => $environment,
24    'streams' => [
25        'schemes' => [
26            'user' => [
27               'type' => 'ReadOnlyStream',
28               'prefixes' => [
29                   '' => ["user/{$folder}"],
30               ]
31            ]
32        ]
33    ]
34];

setup_subdirectory.php:

PHP
 1<?php
 2/**
 3 * Multisite setup for sub-directories or path based
 4 * URLs for subsites.
 5 *
 6 * DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING!
 7 */
 8
 9use Grav\Common\Filesystem\Folder;
10
11// Get relative path from Grav root.
12$path = isset($_SERVER['PATH_INFO'])
13   ? $_SERVER['PATH_INFO']
14   : Folder::getRelativePath($_SERVER['REQUEST_URI'], ROOT_DIR);
15
16// Extract name of subsite from path
17$name = Folder::shift($path);
18$folder = "env/{$name}";
19$prefix = "/{$name}";
20
21if (!$name || !is_dir(ROOT_DIR . "user/{$folder}")) {
22    return [];
23}
24
25// Prefix all pages with the name of the subsite
26$container['pages']->base($prefix);
27
28return [
29    'environment' => $name,
30    'streams' => [
31        'schemes' => [
32            'user' => [
33               'type' => 'ReadOnlyStream',
34               'prefixes' => [
35                   '' => ["user/{$folder}"],
36               ]
37            ]
38        ]
39    ]
40];

When using subdirectories to switch language contexts you might need to load different configs depending on the language. You can place your language specific configs in config/<lang-context>/site.yaml using the example for setup_subdir_config_switch.php below. This way yoursite.com/de-AT/index.html would load config/de-AT/site.yaml, yoursite.com/de-CH/index.html would load config/de-CH/site.yaml and so on.

setup_subdir_config_switch.php:

PHP
 1<?php
 2/**
 3 * Switch config based on the language context subdir
 4 *
 5 * DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING!
 6 */
 7
 8use Grav\Common\Filesystem\Folder;
 9
10$languageContexts = [
11    'de-AT',
12    'de-CH',
13    'de-DE',
14];
15
16// Get relative path from Grav root.
17$path = isset($_SERVER['PATH_INFO'])
18    ? $_SERVER['PATH_INFO']
19    : Folder::getRelativePath($_SERVER['REQUEST_URI'], ROOT_DIR);
20
21// Extract name of subdir from path
22$name = Folder::shift($path);
23
24if (in_array($name, $languageContexts)) {
25    return [
26        'streams' => [
27            'schemes' => [
28                'config' => [
29                    'type' => 'ReadOnlyStream',
30                    'prefixes' => [
31                        '' => [
32                            'environment://config',
33                            'user://config/' . $name,
34                            'user://config',
35                            'system/config',
36                        ],
37                    ],
38                ],
39            ],
40        ],
41    ];
42}
43
44return [];
GPM (Grav Package Manager) and multiple setups

Should you need to manage your subsites' plugins and themes with the GPM, Keep both user/themes + user/plugins, so that the GPM fetches and updates them in a single location. Then symlink the needed items under user/env/my.site.com/themes or user/env/my.site.com/plugins. Then setup individual yaml configurations user/env/my.site.com/config/plugins for each subsites.

Advanced configuration (for Experts)

Once created a setup.php have access to two important variables: (i) $container, which is the yet not properly initialized Grav instance and (ii) $self, which is an instance of the ConfigServiceProvider class.

Inside this script, you can do anything, but please be aware that the setup.php is called every time a user requests a page. This means that memory critical or time-consuming initializations operations lead to a slow-down of your whole system and should therefore be avoided.

In the end, the setup.php has to return an associative array with the optional environment name environment and a stream collection streams (for more informations and in order to set them up correctly, see the section Streams):

PHP
 1return [
 2  'environment' => '<name>',            // A name for the environment
 3  'streams' => [
 4    'schemes' => [
 5      '<stream_name>' => [              // The name of the stream
 6        'type' => 'ReadOnlyStream',     // Stream object e.g. 'ReadOnlyStream' or 'Stream'
 7        'prefixes' => [
 8          '<prefix>' => [
 9            '<path1>',
10            '<path2>',
11            '<etc>'
12          ]
13        ],
14        'paths' => [                    // Paths (optional)
15          '<paths1>',
16          '<paths2>',
17          '<etc>'
18        ]
19      ]
20    ]
21  ]
22]

Tip

Please be aware that a this very early stage you neither have access to the configuration nor to the URI instance and thus any call to a non-initialized class might end in a freeze of the system, in unexpected errors or in (complete) data loss.

Streams

Grav uses URI like streams to define all the file paths in Grav. Using streams makes it really easy to customize lookup paths for any file.

By default, streams have been configured like this:

  • user:// - user folder. e.g. user/
  • page:// - pages folder. e.g. user://pages/
  • image:// - images folder. e.g. user://images/, system://images/
  • account:// - accounts folder. e.g. user://accounts/
  • environment:// - current multi-site location.
  • asset:// - compiled JS/CSS folder. e.g. assets/
  • blueprints:// - blueprints folder. e.g. environment://blueprints/, user://blueprints/, system://blueprints/
  • config:// - configuration folder. e.g. environment://config/, user://config/, system://config/
  • plugins:// - plugins folder. e.g. user://plugins/
  • themes:// - current theme. e.g. user://themes/
  • theme:// - current theme. e.g. themes://antimatter/
  • languages:// - languages folder. e.g. environment://languages/, user://languages/, system://languages/
  • user-data:// - data folder. e.g. user/data/
  • system:// - system folder. e.g. system/
  • cache:// - cache folder. e.g. cache/, images/
  • log:// - log folder. e.g. logs/
  • backup:// - backup folder. e.g. backups/
  • tmp:// - temporary folder. e.g. tmp/

In multi-site setups, some of these default settings may not be what you want. Grav provides easy way to customize streams from the environment configuration using config/streams.yaml. Additionally you can create and use your own streams when needed.

Mapping physical directories to a logical device can be done by setting up prefixes. Here is an example where we separate pages, images, accounts, data, cache and logs from the rest of the sites, but make everything else to look up from the default locations:

user/env/domain.com/config/streams.yaml:

YAML
 1schemes:
 2  account:
 3    type: ReadOnlyStream
 4    prefixes:
 5      '': ['environment://accounts']
 6  page:
 7    type: ReadOnlyStream
 8    prefixes:
 9      '': ['environment://user']
10  image:
11    type: Stream
12    prefixes:
13      '': ['environment://images', 'system://images/']
14  'user-data':
15    type: Stream
16    prefixes:
17      '': ['environment://data']
18  cache:
19    type: Stream
20    prefixes:
21      '': ['cache/domain.com']
22      images: ['images/domain.com']
23  log:
24    type: Stream
25    prefixes:
26      '': ['logs/domain.com']

In Grav streams are objects, mapping a set of physical directories of the system to a logical device. They are classified via their type attribute. For read-only streams that's the ReadOnlyStream type and for read-writeable streams that's the Stream type.

For example, if you use image://mountain.jpg stream, Grav looks up environment://images (user/env/domain.com/images) and system://images (system/images). This means that streams can be used to define other streams.

Prefixes allows you to combine several physical paths into one logical stream. If you look carefully at cache stream definition, it is a bit different. In this case cache:// resolves to cache, but cache://images resolves to images.

Server Based Multi-Site Configuration

Grav 1.7 adds support to customize initial environment from your server configuration.

This feature comes handy if you want to use for example docker containers and you want to make them independent of the domain you happen to use. Or if do not want to store secrets in the configuration, but to store them in your server setup.

The following environment variables can be used to customize the default paths which Grav uses to setup the environment. After initialization the streams may point to different location.

Note

Note: You can use either environment variables or PHP constants, but they need to be set before Grav runs.

Variable Default Description
GRAV_SETUP_PATH AUTO DETECT A custom path to setup.php file including the filename. By default Grav looks the file from GRAV_ROOT/setup.php and GRAV_ROOT/GRAV_USER_PATH/setup.php.
GRAV_USER_PATH user A relative path for user:// stream.
GRAV_CACHE_PATH cache A relative path for cache:// stream.
GRAV_LOG_PATH logs A relative path for log:// stream.
GRAV_TMP_PATH tmp A relative path for tmp:// stream.
GRAV_BACKUP_PATH backup A relative path for backup:// stream.

In addition there are variables to customize the environments. Better documentation for these can be found in Server Based Environment Configuration.

Note

Note: These work also from setup.php file. You can either make them constants by using define() or environment variables with putenv(). Constants are preferred over environment variables.

Variable Default Description
GRAV_ENVIRONMENT DOMAIN NAME Environment name. Can be used for example in docker containers to set a custom environment which does not rely domain name, such as production and develop.
GRAV_ENVIRONMENTS_PATH user://env Lookup path for all environments if you do prefer something like user://sites. Can be either a stream or relative path from GRAV_ROOT.
GRAV_ENVIRONMENT_PATH user://env/ENVIRONMENT Sometimes it may be useful to have a custom location for your environment.

Server Based Configuration Overrides

If you do not wish to store secret credentials inside the configuration, you can also provide them by using environment variables from your server.

As environmental variables have strict naming requirements (they can only contain A-Z, a-z, 0-9 and _), some tricks are needed to get the configuration overrides to work.

Here is an example of a simple configuration override using YAML format for presentation:

YAML
GRAV_CONFIG: true                           # If false, the configuration here will be ignored.

GRAV_CONFIG_ALIAS__GITHUB: plugins.github   # Create alias GITHUB='plugins.github' to shorten the variable names below

GRAV_CONFIG__GITHUB__auth__method: api      # Override config.plugins.github.auth.method = api
GRAV_CONFIG__GITHUB__auth__token: xxxxxxxx  # Override config.plugins.github.auth.token = xxxxxxxx

In above example __ (double underscore) represents nested variable, which in twig is represented with . (dot).

You can also use environment variables in setup.php. This allows you for example to store secrets outside the configuration:

user/setup.php:

PHP
<?php

// Use following environment variables in your server configuration:
//
// DYNAMODB_SESSION_KEY: DynamoDb server key for the PHP session storage
// DYNAMODB_SESSION_SECRET: DynamoDb server secret
// DYNAMODB_SESSION_REGION: DynamoDb server region
// GOOGLE_MAPS_KEY: Google Maps secret key

return [
    'plugins' => [
        // This plugin does not exist
        'dynamodb_session' => [
            'credentials' => [
                'key' => getenv('DYNAMODB_SESSION_KEY') ?: null,
                'secret' => getenv('DYNAMODB_SESSION_SECRET') ?: null
            ],
            'region' => getenv('DYNAMODB_SESSION_REGION') ?: null
        ],
        // This plugin does not exist
        'google_maps' => [
            'key' => getenv('GOOGLE_MAPS_KEY') ?: null
        ]
    ]
];

Caution

WARNING: setup.php is used to set initial configuration. If the plugin or your configuration later override these settings, the initial values get lost.

After defining the variables in setup.php, you can then set those in your server:

 1<VirtualHost 127.0.0.1:80>
 2    ...
 3
 4    SetEnv GRAV_SETUP_PATH         user/setup.php
 5    SetEnv GRAV_ENVIRONMENT        production
 6    SetEnv DYNAMODB_SESSION_KEY    JBGARDQ06UNJV00DL0R9
 7    SetEnv DYNAMODB_SESSION_SECRET CVjwH+QkfnPhKgVvJvrG24s0ABi343cJ7WTPxvb7
 8    SetEnv DYNAMODB_SESSION_REGION us-east-1
 9    SetEnv GOOGLE_MAPS_KEY         XWIozB2R2GmYInTqZ6jnKuUrdELounUb4BIxYmp
10</VirtualHost>
 1location / {
 2    ...
 3
 4    fastcgi_param GRAV_SETUP_PATH         user/setup.php;
 5    fastcgi_param GRAV_ENVIRONMENT        production;
 6    fastcgi_param DYNAMODB_SESSION_KEY    JBGARDQ06UNJV00DL0R9;
 7    fastcgi_param DYNAMODB_SESSION_SECRET CVjwH+QkfnPhKgVvJvrG24s0ABi343cJ7WTPxvb7;
 8    fastcgi_param DYNAMODB_SESSION_REGION us-east-1;
 9    fastcgi_param GOOGLE_MAPS_KEY         XWIozB2R2GmYInTqZ6jnKuUrdELounUb4BIxYmp;
10}
 1location / {
 2...
 3
 4    env[GRAV_SETUP_PATH]          = user/setup.php
 5    env[GRAV_ENVIRONMENT]         = production
 6    env[DYNAMODB_SESSION_KEY]     = JBGARDQ06UNJV00DL0R9
 7    env[DYNAMODB_SESSION_SECRET]  = CVjwH+QkfnPhKgVvJvrG24s0ABi343cJ7WTPxvb7
 8    env[GDYNAMODB_SESSION_REGION] = us-east-1
 9    env[GGOOGLE_MAPS_KEY]         = XWIozB2R2GmYInTqZ6jnKuUrdELounUb4BIxYmp
10}
 1web:
 2  environment:
 3    - GRAV_SETUP_PATH=user/setup.php
 4    - GRAV_ENVIRONMENT=production
 5    - DYNAMODB_SESSION_KEY=JBGARDQ06UNJV00DL0R9
 6    - DYNAMODB_SESSION_SECRET=CVjwH+QkfnPhKgVvJvrG24s0ABi343cJ7WTPxvb7
 7    - DYNAMODB_SESSION_REGION=us-east-1
 8    - GOOGLE_MAPS_KEY=XWIozB2R2GmYInTqZ6jnKuUrdELounUb4BIxYmp
 1putenv('GRAV_SETUP_PATH', 'user/setup.php');
 2putenv('GRAV_ENVIRONMENT', 'production');
 3putenv('DYNAMODB_SESSION_KEY', 'JBGARDQ06UNJV00DL0R9');
 4putenv('DYNAMODB_SESSION_SECRET', 'CVjwH+QkfnPhKgVvJvrG24s0ABi343cJ7WTPxvb7');
 5putenv('DYNAMODB_SESSION_REGION', 'us-east-1');
 6putenv('GOOGLE_MAPS_KEY', 'XWIozB2R2GmYInTqZ6jnKuUrdELounUb4BIxYmp');

In this example, server will also use production environment stored in user/env/production folder.