Copy a site's theme setup — colours, fonts, layout and typography settings — between servers as JSON. Built for cloning a local or production theme onto staging without touching content.
Adds a Theme Transfer tab to Site Config with an export box to copy from and a paste box to import into. No routes, no public endpoints, no files written to public/.
This module is built for the Marmalade theme, developed by Toast. It is not a general-purpose Silverstripe module. It reads and writes the colour and font structures that Marmalade and its supporting modules define, so it only works on a site running the Marmalade theme together with the required modules listed below. On any other theme there is nothing for it to transfer.
- PHP 8.3+
- Silverstripe 6
- The Marmalade theme
toastnz/colourpalettes^6.0toastnz/theme-fonts^6
All of these are required — the module has no fallback behaviour if the colour or font modules are absent, and the theme is what supplies the SiteConfig theme extension that setting_sources points at.
Both module versions must match across the two sites you are transferring between. The payload carries a version number and the import warns on a mismatch, but an older site will not understand a newer payload.
composer require toastnz/theme-transferThen run dev/build?flush=1.
The module knows about colours and fonts on its own. Everything project-specific is configured:
Toast\ThemeTransfer\Services\ThemeTransferService:
# Classes whose $db is harvested for theme settings, and whose has_one
# relations pointing at a Colour become transferable colour slots.
setting_sources:
- Toast\Extensions\SiteConfigThemeExtension
# Extra SiteConfig scalars that do not live on a setting_sources class.
general_fields:
- HeadCodeInjection
- OpenBodyCodeInjection
- FooterCodeInjection
- CompanyStreetAddress
- CompanyEmail
# Fields to drop even if they appear on a setting_sources class.
exclude_fields: []
# many_many relations holding records that reference Colours.
colour_relations:
ThemeButtons:
class: Toast\Models\ThemeButton
match: Title
fields: [Bordered, SortOrder]
colours: [PrimaryColour, SecondaryColour]Once setting_sources is set, new theme fields transfer automatically — add a field to your theme extension and it appears in the payload with no change here. The same applies to new has_one Colour slots.
API keys, mail settings and analytics IDs are per-environment. The export JSON gets pasted into chat, tickets and other sites — keep secrets out of it.
- On the source site: Site Config → Theme Transfer → select all in Export and copy.
- On the target site: paste into Import, leave Preview only ticked, save.
- Read Last import result. It lists what would be created, updated, skipped, and any warnings.
- Untick Preview only and save again to apply.
Importing is idempotent — running the same payload twice creates nothing the second time.
Assets. Logos, images and font files are not included. Upload them on the target.
Theme colours are never created. Colour::IsThemeColour is derived from the theme_colours config by Colour::onBeforeWrite(), and the records are created on dev/build. Only each theme colour's ReferenceColour pointer transfers. If the target lacks a theme colour the payload references, the import warns you to add it to config and run dev/build first.
Because that config lives in your project code, deploying the code to staging creates the slots before you ever paste anything — this only bites when the target is running older code than the source.
Font sources. ThemeFontFaceConfig::FontSrc and FontType are re-derived on write from an uploaded font file, so they cannot be set from data. Face rows are created as placeholders with the right weight and style, and the import warns which file each one expects. Upload the font and link it in the CMS.
Nothing is matched by database ID — IDs differ between servers. Natural keys are used throughout:
| Record | Matched on |
|---|---|
| Colour | CSSName, falling back to Title |
| ThemeFontFamily | Title, falling back to FontFamily |
| ThemeFontConfig | FontConfigID |
| ThemeFontFace | family + weight + style |
| Colour relations | the configured match field |
use Toast\ThemeTransfer\Services\ThemeTransferService;
$service = ThemeTransferService::singleton();
$json = $service->exportJson($siteConfig);
$summary = $service->importJson($siteConfig, $json, true); // dry run
$summary = $service->importJson($siteConfig, $json); // apply$summary returns created, updated, skipped and warnings arrays.