Design, menus, footer and branding

The five full-bleed studios that decide how the site looks, the menu override rule that grants nothing, the two mutually exclusive page editors, and every branding upload with the exact size it expects.

10 min readUpdated 6 August 2026design, branding, menus, footer, logo, pwa

Every decision about how your platform looks is taken somewhere different, and only two of them are on the Settings screen. This page is the map.

Decision Where Permission
Colour, typeface, radius, elevation, motion, navbar and footer layout /admin/design access.design
Which entries the navigation offers /admin/menus access.design
Footer brand line, copyright, socials, link columns /admin/footer access.design
Custom landing pages /admin/builder falls back to access.admin
The built-in Home / About / Contact / Privacy / Terms pages /admin/default-editor falls back to access.admin
Logos, favicons, app icons Settings → Branding (/admin/system/settings) access.settings
One-image logo regeneration /admin/system/logo access.system
Missing coin icons /admin/system/icon view.currency.icon
PWA manifest and install screenshots PWA tab of /admin/system/notification falls back to access.admin

access.design is one grant covering Site Design, Menus and Footer deliberately: whoever can repaint every surface is not a different person from the one who decides what the footer says. It is a real capability worth granting separately from access.settings — see Roles and permissions.

The studios render with no admin header

/admin/design, /admin/design/specimen, /admin/menus, /admin/footer, /admin/default-editor (and its editor) and /admin/builder/{id} are on the admin layout's chromeless list. They are control-rail-plus-live-preview surfaces that own the whole viewport, and the admin header is fixed top-0 z-50 — left in place it sits on top of the studio's own bar and its Save button stops responding.

So when you open one of these there is no top nav. Each studio draws its own bar with its own way back to the admin. Use it rather than the browser Back button, which will drop any unsaved draft.

/admin/appearance is a permanent redirect into /admin/design. It used to own the navbar and footer layout while /admin/design owned colour; both are one screen now. Old bookmarks and the old menu entry still land correctly.

Site Design

/admin/design. Twelve panels in three groups, all previewed live against your real site in an iframe beside the rail.

Theme — Presets, Palette studio, Colours, Type & shape, Accessibility. Chrome — Navbar layout, Footer layout. Components — Tables, Buttons & inputs, Cards, Forms & dialogs, Charts & metrics. Selecting any of these swings the preview to the component specimen at /admin/design/specimen, which is a swatch page of invented rows rather than real data. It is deliberately not in the menu.

Accessibility is the panel to open before you ship a palette. It computes the contrast of what you have built as you type, and it reports rather than prevents — you can ship a 3:1 accent if you want one, but not without being told. "It looked fine to me" is how unreadable text ships.

Which panel is open lives in the URL as ?section=, so a reload keeps your place and you can paste a link to a specific panel into a bug report.

One Save, two stores

The theme is stored as a single platform setting — key designTheme, a JSON document written through PUT /api/admin/system/settings. The navbar and footer layout are stored on the site-chrome row through PUT /api/admin/content/chrome. The Save button reflects "are there unsaved changes on this screen" and writes only the halves that are actually dirty.

The save takes effect on the very next request — the settings fetch is no-store, so there is no cache to wait out. The tab you are looking at repaints itself; other tabs already open elsewhere pick it up on reload.

Reads the navbar, footer, menu overrides and footer content
Saves any subset of them

Four navbar variants — classic (the default), centered, stacked and minimal — and three footer variants — columns (the default), compact and centered. The backend validates the id you send against its own copy of the registry and refuses an unknown one, because an unknown id would render as the default anyway and accepting it would mean telling you a choice was saved that the site never honours.

stacked is the variant that changes the header's height, and the header height is a CSS variable other pages reserve space against. If a page looks like it has a gap or an overlap after a navbar change, that is the thing to suspect.

/admin/menus edits every menu on the site: the Admin sidebar, the Site navigation, and one entry per installed extension for each of its admin and public menus. Each is edited on its own; there is no global menu.

The model has three layers and stores only the middle one:

SHIPPED (code)  ->  OVERRIDE (database)  ->  RESOLVED (what renders)

Nothing here stores a menu. It stores a patch keyed by each shipped item's key, and the resolution rule is: an item nobody mentioned is unchanged. That is why installing an extension adds its entries in their shipped position without touching your edits, and why uninstalling one leaves an inert override entry rather than a broken menu.

Per item you can change the label, the description, the icon, the order (up/down buttons), and whether it is hidden. You can also add items of your own, at the top level or inside any group, with their own label and link.

The override is applied last — after the permission filter and after the extension filter have already run. Two things follow, and both matter:

Hiding is cosmetic. It removes the link. The URL still works for anyone whose role permits it, because the API enforces the same access.* key on the route behind it. Hiding a page is not a way to make it private, and an admin page "hidden" from the menu is still reachable by typing its address.

Un-hiding grants nothing either. Because the override runs on the tree that already survived the permission filter, an order entry naming a key the role cannot reach cannot put it back, and a custom item parented into a group the role cannot see does not resurrect it. If the menu could do that, it would advertise pages the API refuses.

Nothing in the override engine reads or writes permission, and any permission key found in a saved document is stripped before storage. Access is decided on Roles and permissions and nowhere else.

Two more behaviours are worth knowing before you edit:

Renaming an item opts it out of translation. An item's key is its translation path in this codebase, so a label you type cannot participate in it. The renamed item is flagged and its literal text appears in all languages exactly as typed. On a multi-language site that is a real trade, not a detail.

Items you add are shown to everyone who can see that menu, and they are not translated either.

The editor list shows hidden items struck through rather than removing them — an editor that hid what the override hides would be one you could never un-hide anything with.

/admin/footer owns footer content: the brand text, the copyright line, the social links and the link sections. Footer layout — which of the three variants renders — is in Site Design, because you have to see it to judge it.

The link sections use the same override engine as the menus, so the same rule applies: the footer is built from the shipped sections with your patch on top, and a section you hide is a link removed, not a page protected.

The two page editors are mutually exclusive

Settings → General → Landing Page → Landing Page Type, key landingPageType, with two values:

landingPageType Menu entry that appears Screen
CUSTOM Page Builder /admin/builder
DEFAULT Default Pages /admin/default-editor

Only one is ever in the menu. If you cannot find the page builder, this setting is why. When the setting has never been saved the menu treats it as DEFAULT, so a fresh install shows Default Pages.

Page Builder (/admin/builder) is the visual canvas over the CMS page table. Creating a page from a template writes a DRAFT row and drops you into /admin/builder/{id}. The slug rules — including why some slugs are refused outright — are covered in Blog, media, announcements and market news.

Default Pages (/admin/default-editor) edits the five built-in routes and nothing else: home, about, contact, privacy and terms. Home is a structured document with its own section rail; the other four are one HTML body in a rich-text editor. Each row links out to its live public path so you can check the result.

Neither route is in permissions.json, so both open for anyone holding access.admin.

Branding uploads

Settings → Branding (/admin/system/settings, the tab labelled Branding, internally logos). Every field is an image upload and every one declares an expected size. Upload at the stated size — the field is not a resizer.

Site Logos

Setting key Field Expected
logo Square Logo (Light) 96 × 96
darkLogo Square Logo (Dark) 96 × 96
fullLogo Full Logo (Light) 350 × 75
darkFullLogo Full Logo (Dark) 350 × 75
cardLogo Card Logo — sharing cards and previews 256 × 256

Faviconsfavicon16 (16 × 16), favicon32 (32 × 32), favicon96 (96 × 96).

Apple touch icons — nine of them, each its own key and its own size: appleIcon57, appleIcon60, appleIcon72, appleIcon76, appleIcon114, appleIcon120, appleIcon144, appleIcon152, appleIcon180.

Android iconsandroidIcon192, androidIcon256, androidIcon384, androidIcon512. The 512 × 512 is the one the PWA install prompt uses; skip it and installing the app to a home screen produces a blank tile.

MicrosoftmsIcon144 (144 × 144), the Windows tile.

Which logo the header actually draws is a separate setting: Settings → General → Appearance → Navbar Logo Display, key navbarLogoDisplay, with SQUARE_WITH_NAME (square logo plus the site name) or FULL_LOGO_ONLY. Upload a full logo and it still will not appear in the navbar until this is set to FULL_LOGO_ONLY.

The Branding tab writes settings keys through the ordinary settings save, so the changed-keys-only contract applies — see Settings reference. The images themselves land under frontend/public/uploads, which is not in the database backup; see Backup and restore.

/admin/system/logo is a different tool from the Branding tab, and it is not in the admin menu — you reach it by typing the URL.

Rewrites every logo variant from one uploaded image

You upload one image and pick logo or logo-text. The endpoint resizes it into roughly forty named files under frontend/public/img/logo/logo, logo-text, favicon-16x16 through favicon-96x96, every apple-icon-*, android-icon-*, android-chrome-*, ms-icon-* and mstile-* — writing each as both PNG and WebP under its existing filename.

That is the difference between the two screens. The Branding tab stores a path per setting key; this screen overwrites the files on disk that the shipped markup already points at. It is the fast path for a rebrand and the slow path for precision, because every size is derived from the one image you gave it.

Currency icons

System → System Monitoring → Currency Icons, /admin/system/icon. This is not branding — it is the coin logos beside every symbol in the product — but it is the same class of job and it reopens on its own, because exchange providers list new tokens continuously.

Reports which currency icons are missing
Fetches the missing ones and writes 64x64 webp files

Reading the report needs view.currency.icon; fetching needs the separate edit.currency.icon, and without it the sync controls are disabled while the page still renders.

The report scans every catalogue table that defines a tradable asset, normalises each symbol exactly the way the frontend does, and counts four things: Missing icons, Resolvable now, Icons on disk, and Blank placeholders — files that exist but are byte-identical copies of generic.webp, which draw as an empty coin and would otherwise look present. Rows can be filtered by asset class (cex, eco, fiat, fx) and by resolution outcome, and the scan is cached for 60 seconds, so Rescan is the button that forces a fresh one.

The sync resolves each symbol through a fixed chain — local-blockchains → alias → trustwallet → tokenlist → coingecko → flag → alias-word — and writes 64 × 64 alpha WebP files into frontend/public/img/crypto. It is idempotent: a symbol that already has a real icon is skipped, so re-running only fills what is still missing. Fiat codes always resolve to a flag and are never given a crypto logo.

Two options on the panel matter:

  • Enabled only restricts the run to symbols on enabled rows — the icons a customer can actually see today. Most imported currencies are created disabled, so the unrestricted list is largely latent. Use this.
  • Offline limits the chain to local-blockchains and alias. Fast, and it cannot be rate-limited.

The request is synchronous, so the screen sends limit: 200 and a 90-second timeout — shorter than a typical reverse-proxy read timeout, because a longer run just produces a response nobody receives. A full backfill of roughly two thousand icons belongs on the CLI: npm run icons:sync from the backend.

PWA manifest

The PWA editor is the PWA tab of System → Communication Tools → Notification Service (/admin/system/notification) — not a screen of its own, which is why it is hard to find.

Reads manifest.json plus its icons and screenshots
Writes manifest.json
Uploads a screenshot or splash image
Removes one

It edits frontend/public/manifest.json directly — a file, not a table — and it is gated on the settings permissions rather than on a PWA-specific key.

Five sub-tabs:

  • Generalname, short_name, description, start_url (default /), scope (default /), text direction and categories.
  • Appearancedisplay (fullscreen, standalone — the default — minimal-ui or browser), orientation, background_color (default #ffffff) and theme_color (default #000000).
  • Icons — square PNG or WebP, saved into img/logo. The 512 × 512 is the one used for the install prompt; maskable icons need their important content inside the centre 80%.
  • Screenshots — what a customer sees on the install prompt. Each carries a form factor: wide (16:9, 1280 × 720 or larger) for desktop and narrow (9:16, 720 × 1280 or larger) for mobile, plus an optional label. Include at least one of each.
  • Shortcuts — the quick actions offered from the installed app icon.

Changing the manifest does not re-prompt anyone who has already installed the app; browsers refresh a manifest on their own schedule.