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:
scriptRunneris optional — extensions don't need one.mainAppPanelis the visible UI panel;scriptRunneris the headless background runner.- Both can coexist — an extension can have both surfaces (or just one).
pathis relative to your built assets (same asmainAppPanel.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.
<!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:
kittl.ai— credit spending, AI generation, status handlerskittl.design— read and modify design elementskittl.state— canvas mode, selections, viewportkittl.context— user and project contextkittl.stateless— real-time messaging between surfaceskittl.auth— OAuth flowskittl.font— custom font upload
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.