Developer docs  /  Themes

Base themes & org overlays

How org themes overlay the base theme

All theme files live in Azure blob storage under two prefixes (ThemeV2FileStore.cs):

themes-v2/{themeId}/theme/{filePath} ← the shared base theme
themes-v2/{themeId}/overrides/{organisationId}/{filePath} ← per-org edits

Resolution is override-first, per file:

// Try override first
var overridePath = $"themes-v2/{themeId}/overrides/{organisationId}/{filePath}";
if (await blobService.CheckBlobExistAsync(Container, overridePath, ct)) { ... return override; }

// Fall back to base theme
var basePath = $"themes-v2/{themeId}/theme/{filePath}";

An org can also create files that don't exist in the base (they simply live in the overrideprefix). Editing goes through AdminThemeV2Service, which validates before writing: 512 KB sizecap, extension-per-directory allowlist (layouts|partials|snippets|sections|templates.fluid, schema.json, assets.css/.js/.json), the {% schema %} JSON mustdeserialise, and the Fluid must parse — a broken template is rejected at save time instead of500-ing the storefront. Deleting an override reverts the file to base. Every write bumpsCacheVersion and logs to the edit timeline. Snapshots ("restore points") zip all overridesplus the six settings/content JSON columns; restore unpacks them and bumps the version.

Startup seeding — disk → blob by content hash

ThemeV2SeedHostedService runs on every app start and syncswwwroot/themes/origin/ to themes-v2/{themeId}/theme/:

  1. Picks the base theme deterministically (lowest Id among active ThemesV2 rows).
  2. Reads schema/theme.json and syncs its name/version onto the DB row — the version is what makes "update available" appear for orgs (ProvisionedVersion vs base Version, compared numerically). Bump version in theme.json when you ship theme changes.
  3. Lists existing blobs once, then for each disk file compares MD5 of the bytes against the blob's stored ContentHash and only uploads on mismatch.
  4. Prunes blobs with no matching disk file (this is what retired the legacy .hbs files).
  5. Only allowlisted extensions ship: .fluid .hbs .css .js .json .html .svg .png .jpg .jpeg .gif .webp; editor temp files are skipped.

Org overrides are untouched by seeding — they keep layering over whatever the new base is.ApplyBaseThemeUpdateAsync is the per-org "take the update" action: it snapshots first, thenre-stamps ProvisionedVersion and bumps the cache version so the org re-renders against thenew base files.

Note: the seeder is currently hard-wired to one theme folder (ThemeName = "origin") and oneDB row. Building a separate theme today means either forking the origin folder in place (itbecomes the base theme) or authoring your theme entirely as org-level override files throughthe admin file API. Multi-base-theme support would need the seeder and thelowest-Id-wins picks generalised.