Making mods

A mod is a folder with a mod.json and the files it names: a stylesheet laid over Caspian's window, and CSS and JavaScript that run on Caspian's own pages — the new tab, history, downloads and the rest — with settings you declare and the user sets on the mod's card. Several mods run at once; each has a switch. Mods never touch websites.

mod.json

name and version are required, and at least one of style or pages. Paths are relative to the folder and must stay inside it.

mod.jsonjson
{
  "name": "Clock on the new tab",
  "version": "1.0.0",
  "author": "You",
  "description": "A clock above the search box on the new tab page.",
  "homepage": "https://example.com/clock",
  "icon": "icon.png",
  "style": "window.qss",
  "pages": [ { "match": ["newtab"], "css": "clock.css", "js": "clock.js" } ],
  "settings": [
    { "key": "showDate", "label": "Show the date", "type": "bool", "default": true },
    { "key": "hours", "label": "Hours", "type": "select", "options": ["12", "24"], "default": "24" }
  ]
}

Scripts and styles on Caspian's pages

Each pages entry names the pages it applies to — newtab, history, downloads, bookmarks, readinglist, extensions, sites, about, blocked, player, welcome, error, feedback, or * for all of those. The CSS is added as a <style>; the script runs once the page has loaded, as a function that receives caspianMod:

clock.jsjs
const box = document.createElement('div');
box.className = 'modclock';
box.innerHTML = '<div class="t"></div><div class="d"></div>';
const form = document.getElementById('form');          // the new tab's search box
form.parentNode.insertBefore(box, form);
const hour12 = caspianMod.settings.hours === '12';
function tick() {
  const now = new Date();
  box.querySelector('.t').textContent = now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12 });
  box.querySelector('.d').textContent = caspianMod.settings.showDate ? now.toLocaleDateString([], { weekday: 'long', day: 'numeric', month: 'long' }) : '';
}
tick(); setInterval(tick, 1000);
caspianMod.settings

The values of the settings declared in mod.json, as the user set them. Changing one re-runs the mod's pages.

caspianMod.palette

The current colours — dark, bg, bgAlt, surface, border, text, muted, accent, accentText — read from the page's CSS variables, which already carry the theme.

caspianMod.open(url)

Open an https:, http: or caspian: link in a new tab.

caspianMod.storage

get(key), set(key, value), remove(key): JSON values kept per mod.

caspianMod.id / .name / .page

Which mod this is and which page it is running on.

What a mod script can't reach
Mod scripts run in a JavaScript world of their own: they see and change the page's DOM but not the page's own script globals, and not the bridge object Caspian's pages use — the one that reaches history, passwords and settings. They never run on websites, on the settings page or on the passwords page; those names are refused in mod.json and checked again at run time. Open a page in DevTools (F12) to find its selectors and its CSS variables (--bg, --bgAlt, --surface, --border, --text, --muted, --accent, --accentText).

Settings

Declared fields show on the mod's card. Types: bool (a switch), text, number, select (with options). Keys are letters, digits and underscores.

A stylesheet over the window

style names a Qt stylesheet appended after Caspian's own (and after the theme's), so its rules win. It may use the same tokens as Caspian's theme.qss — {{bg}}, {{bgAlt}}, {{surface}}, {{border}}, {{text}}, {{textMuted}}, {{hover}}, {{pressed}}, {{accent}}, {{accentText}}, {{bgHover}} — which resolve to the current palette.

window.qsscss
QWidget#UrlBar { border-radius: 8px; background: transparent; }
QWidget#UrlBar:hover { background: {{bgHover}}; }
QWidget#UrlBar[focused="true"] { background: {{bg}}; border: 1px solid {{accent}}; }

Selectors

The widget object names in Caspian's theme.qss: QWidget#NavBar, QWidget#UrlBar, QWidget#TabStrip, QLineEdit#UrlInput, QMenu#LiquidMenu, QToolButton#NewTabButton, QWidget#BookmarksBar, QWidget#FindBar, #InfoBar and more. Qt's stylesheet syntax is CSS-like but not CSS: no variables, no calc, no !important (see Qt's style sheet reference). The tab strip, the sidebar and the address-bar suggestions paint themselves from the palette, so a stylesheet can't recolour them — a theme does that.

Trying it

Zip the folder and use Install from file… on caspian:mods; a freshly installed mod is on. While editing, Reload on the card re-reads the installed copy from %LOCALAPPDATA%\Caspian\mods\<id> on Windows (~/Library/Application Support/Caspian/mods on macOS) and reloads the pages it runs on. A mod that breaks a page or the window is switched off from the mods page, which no mod can touch.

Publishing

Upload the zip in the store's admin like an extension. The store reads mod.json, marks the listing as a mod, and signs the package; Caspian installs it onto the mods page with the verified badge.