Skip to content

Skin format

A skin is the skin/ folder of its project (MCP server). The extension applies it once the project has a valid skin/skin.json.

skin/
skin.json metadata, and which files make up the skin (required)
trigger.js decides where the skin is on (required)
style.css optional
content.html optional
behavior.js optional

The file names are yours to choose, except skin.json: it names the others.

interface SkinJson {
name: string; // the skin's title in the side panel; at most 40 characters
description: string; // what it does; at most 160 characters
activeWhen: string; // where it is on, in plain words, for people; at most 80 characters
trigger: string; // file in skin/ with the trigger, e.g. "trigger.js"
css?: string; // file in skin/ with CSS
html?: string; // file in skin/ with HTML
js?: string; // file in skin/ with JavaScript
}
  • The text fields are single-line and non-empty. Unknown keys are rejected.
  • File fields are paths inside skin/: letters, digits, ., _, - and /, no ...

The body of an async function, run in the page on each page load and in-page navigation. The skin is on only while it returns true.

// What the body can use:
declare const url: string; // location.href when the trigger started
declare const document: Document; // the page's document
declare const location: Location; // the page's location
// It returns: true to apply the skin. Anything else means no.
  • Only the literal true counts. Any other value, an error, or taking longer than 3 seconds means no.
  • It may await, for example for an element to appear.
  • It runs on every web page you open, for every skin that’s on: check cheap things first, like the hostname, and return early.

When the trigger returns true, once per page view:

Part Applied as
css A stylesheet added to the page
html Inserted into a container at the end of the page’s <body>
js Run once, after the CSS and HTML are in
  • The JavaScript sees the page’s DOM, not the page’s own JavaScript variables. It can’t import anything, and the page’s Content Security Policy doesn’t stop it.
  • Prefer CSS. Use JavaScript only for what CSS can’t do.
  • If your JavaScript watches the page with a MutationObserver, disconnect it while you change the page and reconnect after, and only write what actually changed. Otherwise each change triggers the next and the page never settles.
  • When the trigger stops returning true, the CSS and HTML are removed. Whatever the JavaScript did stays until the page reloads.

A skin that adds a greeting to example.com.

skin/skin.json

{
"name": "Hello world",
"description": "Adds a greeting banner to example.com",
"activeWhen": "On example.com",
"trigger": "trigger.js",
"css": "style.css",
"html": "content.html",
"js": "behavior.js"
}

skin/trigger.js

return location.hostname === "example.com";

skin/content.html

<div class="hello-skin">Hello from your first skin! <span class="hello-time"></span></div>

skin/style.css

.hello-skin {
position: fixed;
right: 16px;
bottom: 16px;
padding: 10px 14px;
border-radius: 8px;
background: #1d1d1f;
color: #fff;
font: 14px/1.4 system-ui, sans-serif;
}

skin/behavior.js

const time = document.querySelector(".hello-skin .hello-time");
if (time) time.textContent = `It's ${new Date().toLocaleTimeString()}.`;

Write these files to a project with writeGadgetFile (MCP server), publish the draft, and open https://example.com.