Redstone Content Solutions is a certified dotCMS partner with deep domain expertise in Enterprise Content Management Systems. Having worked with and evaluated various platforms for over 20+ years, their expertise spans every phase of the content lifecycle, from planning to optimization, and helps dotCMS clients succeed.
Introduction
In the past, maintaining multiple subsidiary sites often led to a fragmented approach, with each site having its own set of templates and components, leading to inconsistencies and increased maintenance overhead. Here at Redstone, we have seen an increased need for enterprise clients to manage multiple sites in their instance that scale easily and can be spun up quickly without developer intervention.
In this article, I will outline our approach to implementing multi-tenant environments in dotCMS that allow for shared theming and components while still allowing for tenant-specific branding and content.
Problem Statement
The main goal in Redstone's approach is to build a maintainable ecosystem of templates, styling, and components that can be shared across multiple tenants while allowing for tenant-specific content and branding.
An instance hosting 12 sites could have 12 separate copies of the theme and its components. Teams running this type of architecture inevitably experience changes and deviations between sites over time, quickly leading to a point where no one knows which version is actually 'correct'.
A key focus for us is being able to update shared theming and components in one place and have those changes propagate to all tenants without breaking tenant-specific customizations and content.
Solution Introduction
Redstone's solution centers on a three-site architecture within dotCMS, where each site serves a specific purpose in maintaining shared resources while enabling tenant customization:
Base Theme Site: Contains all shared templates, CSS, JavaScript, and components common across tenants. It serves as the foundation and widget library for all tenant sites.
Shared Digital Assets: Hosts digital assets (images, documents, etc.) accessible across multiple tenants, eliminating asset duplication and providing a central repository for all content contributors.
Skeleton Site: Acts as a blueprint for new tenant sites, including the necessary structure, configurations, and references to the Base Theme and Shared Digital Assets. New tenants are spun up from this skeleton, ensuring consistency in setup.
Variables such as colors, logos, and fonts are managed through adding inputs to the host content type, allowing each tenant to override these settings without altering the shared codebase.
Solution Steps
1. Extend the host content type
To allow for tenant-specific branding and customization, we first extend the host content type to include configurable variables.
Within the host content type, add fields for items that are commonly customized on a per-tenant basis. These variables might include:
Primary and secondary colors
Logos
Font choices
2. Create The Base Theme Site
We start by creating the theme site, which houses all shared resources. This is the site where your application directory will live, containing all the templates, CSS, JavaScript, and VTL files that will be used across tenant sites. We typically like to name this site something like "Code", as it represents the core codebase for all tenants.
The real magic happens with your theme directory in this code site and how it interacts with the host content type.
The template.vtl for your theme that is used across all tenant sites makes use of the variables set in the host content type. It can then dynamically populate the values for site-specific variables.
You can access the active site's variables you set in the content type in your theme vtls. Below is an example of how to access these variables within your template.vtl file to set the Google Analytics ID dynamically based on the tenant site being viewed.
You can access the active site's variables you set in the content type in your theme vtls. Below is an example of how to access these variables within your template.vtl file to set the Google Analytics ID dynamically based on the tenant site being viewed.
## in this example, we are accessing the host content type to get the google analytics id which we configured within the host content type
## set a variable to hold the host object of the site that is being viewed
#set($hostObject = $dotcontent.find($host.identifier))
## we can then access the google analytics id field from the host content type
#if($UtilMethods.isSet($hostObject.googleAnalyticsGtagId))
<script async src="https://www.googletagmanager.com/gtag/js?id=$hostObject.googleAnalyticsGtagId"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '$hostObject.googleAnalyticsGtagId');
</script>
#endSetting up host content type fields and referencing them in your vtls may seem over-engineered at first, but when you find yourself maintaining 10+ tenant sites, the ability to make a quick update to a variable in the host content item without touching any code is a huge time saver and saves you hours of development and maintenance work down the line, since you don't need to change the code for each tenant.
Referencing Base Theme JS and CSS in the template.vtl
Component js and theme CSS files that are part of your base theme site are also made available within tenant sites via the template.vtl. You must be cautious when referencing these files directly from the base theme site directories in the theme, as this can lead to CORS issues when rendering the tenant sites if not referenced properly.
❗Referencing js and CSS from the base theme site within tenant sites via the template.vtl will likely cause CORS without site aliases being created/used and specific rules set for the site via the admin dashboard to allow specific origins for requests.
❗ It should be noted that outside of using #dotParse() or another server-side inclusion method, using the relative path like //Code/application/themes/multisite-theme/js/components/... will cause 404 errors due to the browser not having that context.
To access JavaScript and CSS files from the base theme site in tenant sites, you will need to access them either using a site alias attributed to the base theme site (e.g., code.yourdomain.com) and setting up dotCMS rules for the tenant sites in your dashboard or reference those files using the dA/resource/ path as we would recommend.
## Example of accessing multiple js files in a directory from the base theme site in tenant sites using their dA/resource/ path
## set a variable to hold the base theme site's identifier
#set($codeSiteIdentifier = $dotcontent.pull("+contentType:Host +title: Code +languageId:1", 1, "modDate desc").first.identifier)
## create a script tag for each js file in the base theme site's application/themes/multisite-theme/js/components/ directory
#foreach($jsFile in $dotcontent.pull(
"+contentType:FileAsset
+conhost: ${codeSiteIdentifier}
+languageId:1
+FileAsset.fileName_dotraw:*.js
+parentpath: /application/themes/multisite-theme/js/components/",
50,"score,modDate desc")
)
<script type="module" src="${jsFile.shortyUrlInode}"></script>
#end3. Create The Shared Digital Assets Site
Next, we create a separate site dedicated to shared digital assets. This site will store images, documents, and other media files that need to be accessible across multiple tenant sites. This site can be structured however makes sense for your organization, but the key is that when an image or other asset is created, its site association is set to this shared assets site in a folder structure that is easily navigable and organized, so that when a tenant site contributor is looking for a shared asset, they know exactly where to find it.
❗It is important that permissions for this site be set so that all tenant site contributors have at least read access to the assets in this site, or they will not be able to use those elements when building pages/assets.
4. Create The Skeleton Site
Finally, we create a skeleton site that serves as a template for new tenant sites. This skeleton site includes the necessary structure, configurations, and references to the Base Theme and Shared Digital Assets. When a new tenant is onboarded, a new site can be created by duplicating this skeleton site, ensuring consistency in setup.
Build the pages that are present on all tenant sites (e.g., home, about, contact)
Set the theme/templates to be used on skeleton pages when they are set up to use the base theme site created in step 2.
Set up the default site configurations in the host content item for the skeleton site as a starter template for new tenant sites to edit.
❗Administrators should do their best to have their skeleton as complete as possible before starting to copy out new tenant sites. This will reduce the amount of rework needed later on, so you don't have to make changes to every tenant site created from an out-of-date skeleton.
5. Onboard New Tenant Sites
With the skeleton site in place, onboarding new tenant sites becomes a streamlined process:
Duplicate the skeleton site to create a new tenant site.
Update the host content type variables to reflect the new tenant's branding and identity.
Add any tenant-specific content while leveraging the shared templates and assets from the Base Theme and Shared Digital Assets sites.
Key Benefits
This three-site architecture provides centralized management of shared templates and assets, with updates maintained in a single location rather than duplicated across tenants.
When shared components need to change, those updates propagate across all tenant sites automatically, significantly reducing maintenance overhead. The approach ensures consistency in look and feel across all tenant sites while still allowing for individual customization through host content type variables.
Most importantly, this structure enables scalability, meaning new tenant sites can be onboarded quickly by duplicating the skeleton site and configuring tenant-specific branding, rather than building from scratch each time.
Conclusion
Managing multiple tenants in a single CMS instance gets messy fast without a solid architecture. The three-site approach gives you centralized control over shared resources while keeping tenant customization clean and manageable. By implementing this structure, tenant onboarding times are reduced dramatically, and technical updates are pushed to all tenant sites simultaneously after being pushed to the base. Why waste time and revenue refitting the same components over and over again for each tenant when you can do it once and have it reflected everywhere, while also being able to kick on a new tenant site without developer intervention in a matter of minutes?
To learn more about dotCMS's multi-site features and configuration, visit the dotCMS multi-site management documentation. This article's three-site architecture leverages these native capabilities to provide a maintainable approach for multi-tenant environments.