Skip to main content

Headless Runners (Script Runners)

A headless runner is a sandboxed extension surface that runs in a hidden iframe — no visible UI, no navigation panel, no toolbar button. It has full access to the same window.kittl SDK as panel extensions and is designed for background or automated workflows.

Script runners are always active while the user is on the page — they start automatically when the editor loads and remain running regardless of whether the extension panel is open or closed.

Use Cases

  • AI generation pipelines — register a generator handler, trigger generation, receive streaming status updates without UI
  • Realtime collaboration — subscribe to stateless messages from peers, process data in the background
  • Data synchronization — read/write design elements programmatically based on external triggers
  • Background processing — any automated workflow that doesn't need user interaction

Manifest Configuration

Extensions declare a headless runner via config.embed.scriptRunner in manifest.json:

{
"config": {
"scopes": ["design:state:read", "design:state:write", "ai:credit:spend"],
"embed": {
"mainAppPanel": {
"path": "index.html"
},
"scriptRunner": {
"path": "runner.html"
}
}
}
}

Key points:

  • scriptRunner is optional — extensions don't need one.
  • mainAppPanel is the visible UI panel; scriptRunner is the headless background runner.
  • Both can coexist — an extension can have both surfaces (or just one).
  • path is relative to your built assets (same as mainAppPanel.path).

Creating a Runner

A runner entry point is a plain HTML file that loads the SDK and runs code on ready. There is nothing to render — the iframe is always hidden.

runner.html
<!doctype html>
<html>
<head>
<script src="https://sdk.kittl.com/kittl-sdk.esm.js" type="module"></script>
</head>
<body>
<script type="module">
window.kittl.onReady(async () => {
console.log('Headless runner is ready');

// Example: register as an AI generation handler
const result = await kittl.ai.registerHandler(
'my-generator',
(update) => {
console.log('Generation status:', update.status);
if (update.status === 'COMPLETE') {
console.log('Result:', update.result);
}
},
);

if (result.isOk) {
console.log('Handler registered');
}
});
</script>
</body>
</html>

SDK Access

The runner has access to the exact same window.kittl API as a panel extension:

All API calls go through the same scope enforcement. If the manifest declares ["design:state:read"], the runner can only read — it cannot write.

Lifecycle

  • The runner activates when the extension loads, alongside any main panel.
  • It persists as long as the extension is installed and the editor is open.
  • It is destroyed when the editor unmounts or the extension is uninstalled.

Scoping

Scope enforcement is identical to panel extensions. Declare the required scopes in manifest.json under config.scopes. See SDK Scopes for the full list.