Skip to content
Build a theme B1 5 min read Customisation

Theme overview

What a theme provides — templates, samples, assets.

You've seen one theme already — the shipped default theme rendering the Minimal sample inside /builder.php. To put your brand on top of BuilderJS you'll customise this layer, not the engine. This page tells you what a theme is, when to author one, and when authoring just a sample is enough.

What ships with a theme

A theme is a single folder under demo/themes/<name>/. It contains four kinds of files, and that's it:

  • Templates (*.template.html) — one EJS template per element type. Image.template.html renders an ImageElement, Button.template.html renders a ButtonElement, and so on. The shipped default theme has 30+ templates covering every element the engine surfaces. They are the visual contract — change them and the rendered HTML changes.
  • Samples (master/sample/email/*.json + master/sample/page/*.json) — pre-authored page trees ready to drop into builder.load(themeJson, …). The default theme ships 70+ samples between email + page; every card on /gallery.php is one of them.
  • Assets (master/assets/image/) — content images. The shipped library is 100% AI-generated photo + illustration imagery, royalty-free for any buyer use (RULE-H1 — no copyrighted images, ever). Replace with your brand photography by dropping files in this folder; the JSON src keys reference them by relative path.
  • Manifest (index.json) — metadata: theme name, title, the pages array (which page templates apply), the templates array (which element templates ship), and an optional _brand block of brand tokens.

The whole theme directory is portable — copy it to another BuilderJS install and it works. No SQL migrations, no custom build step. That portability is why themes are the buyer customisation surface, not the engine.

When to author a new sample vs a new theme

Short version:

  • I want one new email layout for my campaignauthor a sample. A new .json file under themes/default/master/sample/email/<your-name>.json plus a thumbnail, and the gallery picks it up.
  • I want my brand colours, my fonts, my logo, my domain-specific element layoutfork the default theme. Copy themes/default/themes/<your-brand>/, edit index.json + replace logos / fonts / templates / samples, register the new theme dir.

Most buyer projects start with the first and graduate to the second. The line crosses when a single sample isn't enough to express your brand — for example, when every email needs the same custom header that no shipped template renders, or when you need a primary colour that isn't in the shipped palette.

Why themes are pluggable, not configurable

You might expect a theme to be "the same default templates plus a CSS override". BuilderJS goes further: a theme owns its templates wholesale. Two reasons:

  1. Templates control rendered HTML, not just styling. A theme that wants <h2 class="brand-headline"> instead of <h1> for a heading element changes one template, not a CSS file. That kind of structural override would be impossible with CSS-only theming.
  2. Email-client compatibility lives in templates. The shipped templates are tuned for the Litmus 22-client matrix (Outlook, Apple Mail, Gmail web, …). When you author a custom theme, you inherit those compatibility decisions; when you fork a template you can re-tune for your audience.

The trade-off: you maintain the templates yourself. The shipped default theme is your reference; the example-mini/ starter is what an author-from-scratch theme looks like (just Page.template.html + Block.template.html + a tiny manifest — under 100 LOC total before you customise it).

The shipped default theme tour

Open demo/themes/default/ and look at the layout:

`master/` is the **buyer surface** — every shipped sample under `master/sample/` appears in the gallery + builder. `others/` is for variants that aren't ready or shouldn't be marketed (work-in-progress samples, internal QA fixtures).

When demo/builder.php boots, the buyer-modifiable line is $builderConfig['defaultTheme'] = 'default' — change it to your fork name and every page in the demo loads your theme instead.

Common buyer questions answered up front

Q: Can I have multiple themes side-by-side? A: Yes. Drop a second theme dir under themes/, register it in ThemeRegistry::SEED (one row per theme), and /gallery.php shows both. The builder accepts a ?theme=<name> query param so each rendered page can pin its theme.

Q: Can I share assets between themes? A: Two patterns. (1) Use the shipped default theme's images by leaving JSON src paths pointing at master/assets/image/... — the engine resolves relative to the active theme but you can hard-link to a known-stable shared path. (2) Symlink the assets dir at the filesystem level — themes/<your-fork>/master/assets → themes/default/master/assets. Both work; the symlink approach is cleaner if your themes share imagery wholesale.

Q: Do I need to write EJS, or can templates be plain HTML? A: They're EJS — <%= var %> and <% if %> blocks. But 90% of every shipped template is plain HTML; the EJS bits inject the JSON-driven values. Read three of the shipped templates and the pattern is obvious.

Did you make it?

You should now be able to:

  • ✅ State the smallest unit you can ship to add your brand to BuilderJS — answer: one new .json sample (or one new theme dir for a deeper change).
  • ✅ Distinguish theme-level customisation from sample-level customisation.
  • ✅ Open themes/default/ and recognise each top-level dir's purpose.

What's next?