⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Brand Setup

Setting up a new brand environment

Docs / Brand Setup
Download .md file
Open .md in new tab

This guide covers how to set up a new brand environment from scratch. When a brand user logs in, the system automatically loads their theme, swaps the logo, updates the home link, and filters the sidebar navigation.

Scope. This walkthrough is for brand instances hosted inside BrandOS — auth-driven theming on the docs site. A standalone consumer site (like the studio site) doesn't use brand instances: it loads one theme.css of its own after the engine CSS. See Setup for that model.

A brand instance is two files plus assets:

File Holds Owner
cms/brands/<brand>/brand.json Admin settings: name, description, Google Fonts URL, footer text, optional favicon and OG image paths Set at onboarding, rarely touched
cms/brands/<brand>/assets/theme.css Designer settings: font slots and roles, palette, semantic colours, dark mode Iterated during design

The generator reads every manifest and builds assets/js/theme-config.js from them on each build. That file is generated output. Never edit it by hand.

By Default itself runs on the same model: instance 0's manifest and theme live at cms/brands/bydefault/, and the engine (design-system.css) ships neutral defaults.


What Brands See

When a brand logs in, the following elements personalise automatically:

Element Default (team/admin) Brand
Logo By Default Brand logo from brand-name/assets/
Home link index.html brand-name/index.html
Theme By Default (instance 0's theme.css) Brand theme.css
Fonts Zalando Sans + ES Face (instance 0's theme.css) Per the brand's theme.css and brand.json fonts URL
Sidebar Full navigation Auto-filtered by page access

All of this is driven by the user's brandFolder value in Netlify Identity.


Quick Checklist

  1. Copy cms/brands/brand-template/ to cms/brands/your-brand/
  2. Edit cms/brands/your-brand/brand.json: name, description, fonts URL, footer
  3. Update cms/brands/your-brand/_defaults.md with access: "brand:your-brand"
  4. Replace the demo logos in cms/brands/your-brand/assets/ with the brand's
  5. Customise cms/brands/your-brand/assets/theme.css
  6. Grant tool access: add your-brand to actionAccess in relevant cms/*.md files
  7. Create a Netlify Identity user with the brand role
  8. Run cd cms/generator && npm run docgen and test in dev mode

There is no separate registration step. A folder with a brand.json and an assets/theme.css is a live brand: the generator adds it to the runtime registry, the switcher, and the build.


Step 1: Create the Brand Folder

Copy the cms/brands/brand-template/ folder and rename it to the brand name. Use lowercase, hyphenated names.

cp -r cms/brands/brand-template/ cms/brands/your-brand/

The template ships dressed as a demo brand (Brand Template), so every file you copy is a working example to replace, not an empty scaffold. The folder contains:

cms/brands/your-brand/
  brand.json      : Admin settings (the manifest)
  _defaults.md    : Access control defaults
  assets/
    theme.css     : Designer token overrides
    logo.svg      : Brand header logo (desktop)
    logo-small.svg : Compact logo variant (mobile)
    logos/        : Brand book logo set (light/dark variants)
  welcome.md      : Welcome page (optional)
  brand-book.md   : Brand book intro copy (optional)

On build, the generator copies assets/ to the output, generates the brand's pages, and builds the brand book from the logos/ folder. Nothing is hand-copied.

Set up access defaults

Update cms/brands/your-brand/_defaults.md:

---
access: "brand:your-brand"
---

This ensures all pages generated for this brand are scoped to their access level.


Step 2: Fill in the Manifest

Edit cms/brands/your-brand/brand.json:

{
  "name": "Your Brand",
  "description": "One line shown on the brand's home page.",
  "googleFontsUrl": "https://fonts.googleapis.com/css2?family=YourFont:wght@400..700&display=swap",
  "footerText": "© 2026 Your Brand"
}
Key Required Description
name Yes Display name: the switcher label, page titles, and the brand home hero
description No Hero copy on the brand's index page
googleFontsUrl No Google Fonts URL, injected on brand pages and at runtime. Use null for self-hosted fonts
typekitId No Adobe Typekit kit ID (e.g. wgr3lwl). Emits the kit stylesheet link into the brand's page heads
fontPreload No Array of site-relative font file paths to preload (prevents FOUC on the primary face)
footerText No Brand footer. Falls back to the site footer when omitted
faviconSvg / faviconIco No Absolute site paths to a brand favicon. Fall back to the site set
ogImage No Absolute site path to the brand's share image. Falls back to the site default

Font sources are per brand: a page head only carries the Typekit kit, preloads, and Google Fonts its own manifest declares. Self-hosted fonts pair a fontPreload entry with @font-face declarations in the brand's theme.css (see Step 4); the runtime preview switcher loads Google Fonts only, so Typekit-served faces show on the brand's own pages, not in a preview from another space.

The folder name is the brand key. It must match the brandFolder value in Netlify Identity.

Note: Tool access is NOT configured here. It is controlled via actionAccess in cms/*.md frontmatter. See the Access Control docs for details.


Step 3: Add Brand Logos

Logo files live in the brand source folder with fixed names:

cms/brands/your-brand/assets/logo.svg        : Full header logo (desktop)
cms/brands/your-brand/assets/logo-small.svg  : Compact variant (mobile, future use)
cms/brands/your-brand/assets/logos/          : Brand book set, logo_brand-<type>-<light|dark>.svg

The generator copies these to your-brand/assets/ on build and builds the brand book page from the logos/ set.

Logo requirements

  • Format: SVG
  • Sizing: The container is 50px tall (desktop) / 45px tall (mobile). Width scales automatically. A wide aspect ratio (roughly 2:1) works best.
  • Colour: Use fixed colours, or currentColor if you want the logo to adapt to the theme's text colour and dark mode.
  • Cleanup: Run logos through the SVG Cleaner before adding them:
node assets/js/svg-clean.js --strip-comments --size -o cms/brands/your-brand/assets/logo.svg <<'SVGEOF'
<svg>...paste logo SVG here...</svg>
SVGEOF

How it works

When a brand logs in, theme-loader.js fetches the logo from your-brand/assets/logo.svg and injects it into the header. When an admin previews a theme, the logo swaps too. When the theme is unloaded, the By Default logo is restored.


Step 4: Customise the Theme

Open cms/brands/your-brand/assets/theme.css and update the token values. The copied file is the Brand Template demo theme, a complete working example of the override surface.

What to override

Category Tokens Purpose
Typography … , plus the roles , , , Brand fonts. Add @font-face in the theme file if self-hosting
Neutral scale through The grey ramp behind surfaces and text
Text , , , , Core text colours
Backgrounds , Surface colours
Borders , , Border and input outline colours
Accent , , , --selection-* Accent colour across UI

Buttons are not a theme surface: they derive from --text-primary and --text-inverted at component scope and re-skin through those tokens.

Tokens you usually don't need to override

  • --text-faded, --background-faded, --border-faded: alpha-based, work across any theme
  • --status-*-bg: background tints derived from the accent primitives
  • Spacing, radius, type scale, breakpoints: engine rhythm, not brand expression

Dark mode

Theme the [data-theme="dark"] block, then mirror it exactly inside the @media (prefers-color-scheme: dark) fallback for users without JavaScript. Drift between the two blocks is a known failure mode; keep them identical.

How it works

Brand pages link the theme statically after the design system CSS; at runtime theme-loader.js injects the same file when a brand user logs in or an admin previews the brand. Later token values win the cascade, so the theme overrides the engine's neutral defaults and the whole site follows.


Step 5: Create the Netlify Identity User

In the Netlify Identity dashboard, create a new user and add their brand name as a role tag (e.g. your-brand). The system automatically recognises non-hierarchy role tags as brand folder names.

You can also add brand as a second tag for clarity, but it's not required. A single tag like your-brand is sufficient.

Tag Required? Purpose
your-brand Yes Maps to the brand folder name, manifest key, and logo path
brand Optional Explicitly sets the access level (auto-inferred if omitted)

The brand name tag is the single identifier that ties everything together:

  • Manifest: cms/brands/your-brand/brand.json
  • Theme CSS: your-brand/assets/theme.css
  • Logo: your-brand/assets/logo.svg
  • Home link: your-brand/index.html
  • Page access: restricted to your-brand/ folder + shared tool pages

Step 6: Test in Dev Mode

On localhost, the dev toolbar lets you switch roles without logging in.

Test as brand

  1. Open assets/js/auth-config.js
  2. Set devMode.brandFolder to your brand name:
    devMode: {
      defaultRole: 'brand',
      email: 'dev@bydefault.studio',
      brandFolder: 'your-brand'
    }
    
  3. Open any page on localhost
  4. Switch to the brand role in the dev toolbar
  5. Verify: logo swaps, home link updates, theme loads, sidebar filters

Test as admin with theme preview

  1. Set devMode.defaultRole back to 'admin'
  2. Open any page on localhost
  3. Click the user icon in the header, open the dropdown
  4. Under Theme Preview, select your brand's theme
  5. Verify: theme loads, logo swaps, persists across page navigation
  6. Clear the preview to return to By Default

Creating from Template: Full Walkthrough

Here is the complete workflow for adding a new brand called "acme":

# 1. Copy the brand source template
cp -r cms/brands/brand-template/ cms/brands/acme/

# 2. Clean and add logos
node assets/js/svg-clean.js --strip-comments --size \
  -o cms/brands/acme/assets/logo.svg <<'SVGEOF'
<svg>...paste full logo...</svg>
SVGEOF

node assets/js/svg-clean.js --strip-comments --size \
  -o cms/brands/acme/assets/logo-small.svg <<'SVGEOF'
<svg>...paste small logo...</svg>
SVGEOF

Then manually:

  1. Edit cms/brands/acme/brand.json: name "Acme", description, fonts URL, footer
  2. Edit cms/brands/acme/assets/theme.css: replace the demo values with Acme's fonts and colours
  3. Replace the demo SVGs in cms/brands/acme/assets/logos/ with Acme's light/dark set
  4. Update cms/brands/acme/_defaults.md:
    ---
    access: "brand:acme"
    ---
    
  5. Edit cms/brands/acme/welcome.md and brand-book.md if needed
  6. Grant tool access, edit the tool markdown files to include acme:
    • cms/display-ad-preview-docs.md: change actionAccess: "brand:brand-template" to actionAccess: "brand:brand-template,acme"
    • Update the matching data-access on tools/display-ad-preview.html
  7. Run cd cms/generator && npm run docgen
  8. In Netlify Identity, create a user with:
    { "roles": ["brand"], "brandFolder": "acme" }
    
  9. Test in dev mode

Architecture Reference

File Purpose
cms/brands/<brand>/brand.json The manifest: admin settings. With assets/theme.css alongside it, the brand is live
cms/brands/<brand>/assets/theme.css Designer token overrides
assets/js/theme-config.js GENERATED runtime registry, built from the manifests on every docgen. Never hand-edit
assets/js/theme-loader.js Loads/unloads theme CSS, fonts, logo, and home link dynamically
assets/js/auth.js Calls initThemeForUser() after auth resolves
assets/js/auth-config.js Role hierarchy, dev mode settings (including brandFolder for testing)
scripts/theme-regression/check.py Instance-0 token regression check: run after editing the engine's tokens or By Default's theme

Instance 0 is the exception that proves the model: cms/brands/bydefault/ holds By Default's manifest and theme, the root site loads that theme as a static link on every page, and the brand does not appear in the switcher, because clearing a preview is the return to By Default.

Data flow

Build
  → generator reads every cms/brands/*/brand.json
  → brands with assets/theme.css enter the runtime registry
  → theme-config.js is emitted in one write
  → brand pages are generated with the brand's chrome (title, footer, favicon, OG)

Login
  → auth.js resolves role + brandFolder from app_metadata
  → auth.js calls initThemeForUser(user)
  → theme-loader.js reads brandFolder
  → Sets home link to brandFolder/index.html
  → Swaps logo to brandFolder/assets/logo.svg
  → Loads brandFolder/assets/theme.css (if in the registry)
  → Injects the Google Fonts link (from the manifest, via the registry)
  → Auth filters sidebar navigation by access level
  → Page renders with full brand branding
On this page
  • What Brands See
  • Quick Checklist
  • Step 1: Create the Brand Folder
  • Step 2: Fill in the Manifest
  • Step 3: Add Brand Logos
  • Step 4: Customise the Theme
  • Step 5: Create the Netlify Identity User
  • Step 6: Test in Dev Mode
  • Creating from Template: Full Walkthrough
  • Architecture Reference
Previous

Access Control

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default