Skip to content
Build a theme B2 6 min read Customisation

Theme anatomy

Inside themes/default/ — name every file purpose.

Now you'll open every file in themes/default/ and name what it does. By the end of this page you should be able to walk a stranger through the theme directory like it's your own.

The directory shape, again — but with reasons

Three top-level concepts:
  1. Templates (the rendering contract) live next to index.json.
  2. Samples (the buyer pages) live under master/sample/.
  3. Assets (images) live under master/assets/.

That's the entire theme surface.

index.json — the manifest

Open themes/default/index.json:

Five fields the engine reads:
  • name — the dir name (must match the folder).
  • title — display label for the gallery + theme picker. Buyers see this string.
  • pages — which top-level page-template families this theme provides. Most themes ship Page (the layout for emails / landing pages) and optionally Form (the layout for checkout / signup forms).
  • templates — an array listing every <Name>.template.html file the theme ships. Used by the engine to validate what samples can use.
  • formats + configData — baseline values applied to every page that uses this theme (default text colour, default button colour, default container width, etc.). Per-page overrides win.

You can ignore the rest at first. The five above are enough to register a theme.

Templates — the visual contract

Every element type the engine surfaces has a corresponding <Name>.template.html. The shape:

EJS is the templating language — `<%= var %>` interpolates a JS expression, `<% if (x) { %>` runs JS for control flow. The engine evaluates each template against:
  • The element's own JSON node (src, alt, url, etc. become local vars)
  • The element's formats object (under the local formats var)
  • A small set of theme-wide globals (MEDIA_URL, THEME_CONFIG_DATA, etc.)

Two template categories you'll see:

  • Element templatesP, Heading, Image, Button, Divider, List, … one per leaf element class. These are the bulk of the templates dir.
  • Container templatesPage, Block, Grid, Cell, Form. These define how their children are stacked. The shipped Page template is wrapped for email-client compatibility (table-based layout, MSO conditionals).

Every template is < 200 LOC; most are 30-80 LOC. You can read all 30 in an afternoon.

Samples — the buyer pages

master/sample/email/ holds the email samples. master/sample/page/ holds the landing-page + checkout samples. Each sample is one .json file plus a thumbnail (.png for full-bleed previews, .svg for placeholder layouts) and an optional .html rendering for offline preview.

Pick any sample and open its JSON — you'll recognise the shape from JSON structure. The Minimal.json sample is the smallest meaningful example (≈ 250 lines); WelcomeCustomers.json and SitewideSale.json are richer (~1000 lines). Pick by your appetite.

Assets — content images

master/assets/image/email/ and master/assets/image/page/ hold every content image used by samples — hero photos, product shots, social-icon glyphs, brand glyphs. The shipped images are AI-generated for a clean royalty-free shipping story (RULE-H1).

When a sample's ImageElement references "src": "master/assets/image/email/logo.png", the engine resolves it as MEDIA_URL + 'master/assets/image/email/logo.png'. MEDIA_URL is the fourth global in the bundle — for the shipped demo it's /themes/default/, so the final URL is /themes/default/master/assets/image/email/logo.png.

Replace any image by dropping a file with the same path + name. The JSON pointing at it doesn't change.

The 8 hard rules every theme must follow

Distilled from THEME_BEST_PRACTICE.md (which is 2800 LOC of rules + rationale; you don't need to read it cover-to-cover, but you will hit it eventually):

  1. No copyrighted images, ever (RULE-H1). Use the shipped AI library, generate your own via the included scripts/core/gen-images.mjs, or buy royalty-free.
  2. Hierarchy is sacred (RULE-H2). Page → Block → Element. Page → Block → Grid → Cell → Block → Element. Don't skip layers.
  3. No hardcoded image sizes except logos + icons (RULE-H3). Content imagery is responsive; only fixed assets like logos / glyphs declare pixel widths.
  4. Aspect-lock on by default (RULE-H4). Image elements preserve aspect ratio unless the buyer explicitly overrides.
  5. 4-colour palette cap (RULE-H8). One sample uses at most 4 distinct colours (background, foreground, accent, muted). More than 4 reads as Geocities.
  6. 5-typography-tier cap (RULE-H10). H1 / H2 / H3 / Body / Caption. Anything beyond is rejected at the audit.
  7. No margin on BlockElement (RULE-H11). Vertical rhythm comes from Block's padding_top + padding_bottom. Margin collapses unpredictably in email clients.
  8. Explicit units in dimensions (RULE-H12). "width": 600 is pixels; never "width": "100%" in a formats field. Percentages live elsewhere (cell widths, container queries) — but a leaf element's width is always typed.

The rules feel pedantic until you've shipped a sample that breaks one and watched it render differently in Gmail vs Outlook. Then they read as protection.

The 5 templates you'll touch most

If you fork the default theme, these are the files most buyers customise:

  • Page.template.html — the outer page wrapper. Where you put your brand's outermost background, container width, fixed header.
  • Heading.template.html — your brand's headline typography (font, weight, line-height).
  • P.template.html — your brand's body typography.
  • Button.template.html — primary CTA shape (border radius, padding, hover state).
  • Image.template.html — usually fine as-is; touch only if you need brand-specific image treatments (rounded corners, drop shadows, polaroid frames).

Almost every other template inherits visual decisions from these five via formats.<key> defaults in the buyer's brand-customised index.json block.

Did you make it?

You should now be able to:

  • ✅ Open themes/default/, name every top-level dir + index.json's purpose without re-reading.
  • ✅ Read an EJS template and predict the rendered HTML for a given JSON node.
  • ✅ State the 8 hard rules and explain why each one exists.
  • ✅ Identify the 5 templates a brand fork would touch first.

What's next?

  • Create a sample ⭐ — the headline lock-step copy-along; you'll author a new email and see it in the gallery in 30 minutes.
  • 🔗 file-tree of demo/themes/default/.
  • 🔗 THEME_BEST_PRACTICE.md for the long-form authoring rules + audit matrix.

Stuck?

  • 🐛 grep themes/default/<TemplateName>.template.html for the exact EJS contract.
  • 🐛 the example-mini theme dir is the smallest possible theme — open it for a 5-file reference of what's required vs nice-to-have.