Extensions API Reference
Every method available to Chatons extensions, with parameters and return types verified against the source code.
New to extensions? Start with the Extensions Tutorial.
Related:
- Extensions Overview -- Concepts and manifest reference
- Extensions Channels -- Channel-specific host APIs
- Extensions UI Library -- CSS classes and UI helpers
- Requirement Sheets -- Inline prerequisite sheets
How Extensions Call Host APIs
All extension APIs live on the window.chaton object. Every call requires your extension ID as the first argument.
var EXT_ID = '@yourname/chatons-my-ext';
// All calls return Promises
var result = await window.chaton.extensionStorageKvGet(EXT_ID, 'key');
There is no window.chatonExtension.api.* namespace. The only API surface is window.chaton.*.
Key-Value Storage
Backed by SQLite (extension_kv table). Namespaced by extension ID.
Required capability: storage.kv
extensionStorageKvGet(extensionId, key)
Read a value by key.
var result = await window.chaton.extensionStorageKvGet(EXT_ID, 'mykey');
// result = { ok: true, data: <value> }
// data is the parsed JSON value (object, array, string, number, null)
// data is null if the key does not exist
extensionStorageKvSet(extensionId, key, value)
Write a value. Creates the key if it does not exist, overwrites if it does.
// Pass objects directly -- no need to JSON.stringify
await window.chaton.extensionStorageKvSet(EXT_ID, 'config', {
apiUrl: 'https://example.com',
enabled: true,
});
// Strings, numbers, arrays also work
await window.chaton.extensionStorageKvSet(EXT_ID, 'counter', 42);
extensionStorageKvDelete(extensionId, key)
Delete a key.
var result = await window.chaton.extensionStorageKvDelete(EXT_ID, 'mykey');
// result = { ok: true, data: { removed: true } }
// removed is false if the key did not exist
extensionStorageKvList(extensionId)
List all keys for this extension.
var result = await window.chaton.extensionStorageKvList(EXT_ID);
// result = { ok: true, data: [
// { key: 'config', value: { apiUrl: '...' }, updatedAt: '2026-03-10T...' },
// { key: 'counter', value: 42, updatedAt: '2026-03-10T...' },
// ] }
File Storage
Sandboxed file-based storage under ~/.chaton/extensions/data/<extensionId>/.
Required capability: storage.files
extensionStorageFilesRead(extensionId, relativePath)
Read a file's content as a UTF-8 string.
var result = await window.chaton.extensionStorageFilesRead(EXT_ID, 'exports/report.md');
// result = { ok: true, data: '# Report\n...' }
// result = { ok: false, error: { code: 'not_found', message: 'file not found' } }
extensionStorageFilesWrite(extensionId, relativePath, content)
Write a file. Creates parent directories automatically.
await window.chaton.extensionStorageFilesWrite(
EXT_ID,
'exports/report.md',
'# My Report\n\nGenerated on ' + new Date().toISOString()
);
Path security: Paths are normalized and sanitized. .. traversal is rejected. Paths with null bytes are rejected.
Events
Subscribe to host events or publish custom events.
extensionEventSubscribe(extensionId, topic, options?)
Required capability: events.subscribe
await window.chaton.extensionEventSubscribe(
EXT_ID,
'conversation.created',
{ projectId: 'optional-filter' }
);
extensionEventPublish(extensionId, topic, payload, meta?)
Required capability: events.publish
await window.chaton.extensionEventPublish(
EXT_ID,
'myext.note.created',
{ noteId: 'abc123', title: 'My Note' },
{ idempotencyKey: 'note-abc123' }
);
Available event topics
| Topic | When Fired |
|---|---|
app.started | App startup complete |
conversation.created | New conversation created |
conversation.updated | Conversation properties changed |
conversation.message.received | New message in a conversation |
conversation.agent.started | AI model started processing |
conversation.agent.ended | AI model finished processing |
conversation.turn.ended | Full turn (user + assistant) completed |
conversation.tool.executed | A tool call was executed |
project.created | New project imported |
project.deleted | Project removed |
extension.installed | An extension was installed |
extension.enabled | An extension was toggled on |
Queue
Persistent job queue backed by SQLite (extension_queue table). Supports at-least-once delivery, retry with backoff, and dead-letter handling.
extensionQueueEnqueue(extensionId, topic, payload, opts?)
Required capability: queue.publish
await window.chaton.extensionQueueEnqueue(
EXT_ID,
'export-jobs',
{ conversationId: 'uuid-...', format: 'markdown' },
{
idempotencyKey: 'export-uuid', // Prevents duplicate enqueue
availableAt: '2026-03-10T15:00:00Z', // Delayed processing
}
);
extensionQueueConsume(extensionId, topic, consumerId, opts?)
Required capability: queue.consume
var messages = await window.chaton.extensionQueueConsume(
EXT_ID,
'export-jobs',
'worker-1',
{ limit: 10 }
);
// messages is an array of { id, payload, ... }
extensionQueueAck(extensionId, messageId)
Mark a message as successfully processed.
await window.chaton.extensionQueueAck(EXT_ID, message.id);
extensionQueueNack(extensionId, messageId, retryAt?, errorMessage?)
Mark a message as failed. It will be retried.
await window.chaton.extensionQueueNack(
EXT_ID,
message.id,
null, // retryAt: null means retry immediately
'Processing failed: timeout'
);
extensionQueueDeadLetterList(extensionId, topic?)
List messages that have exceeded their retry limit.
var dead = await window.chaton.extensionQueueDeadLetterList(EXT_ID, 'export-jobs');
Queue item states
| State | Meaning |
|---|---|
queued | Waiting to be consumed |
processing | Currently being processed by a consumer |
done | Successfully acknowledged |
dead | Failed too many times, moved to dead-letter |
Host Calls
General-purpose host method invocation.
extensionHostCall(extensionId, method, params?)
var result = await window.chaton.extensionHostCall(EXT_ID, method, params);
// result = { ok: true, data: ... }
// or
// result = { ok: false, error: { code, message } }
notifications.notify
Required capability: host.notifications
Show a notification in the Chatons UI.
await window.chaton.extensionHostCall(EXT_ID, 'notifications.notify', {
title: 'My Extension',
body: 'Something happened!',
});
| Parameter | Type | Required | Description |
|---|---|---|---|
title | string | No | Notification title. Defaults to "Notification". |
body | string | No | Notification body text. |
conversations.list
Required capability: host.conversations.read
List all conversations.
var result = await window.chaton.extensionHostCall(EXT_ID, 'conversations.list');
// result.data = [
// {
// id: 'uuid',
// projectId: null, // null for global conversations
// title: 'My Conversation',
// updatedAt: '2026-03-10T...',
// lastMessageAt: '2026-03-10T...',
// modelProvider: 'openai',
// modelId: 'gpt-4o',
// thinkingLevel: null,
// }
// ]
conversations.getMessages
Required capability: host.conversations.read
Read messages from a specific conversation.
var result = await window.chaton.extensionHostCall(
EXT_ID,
'conversations.getMessages',
{ conversationId: 'uuid-...' }
);
// result.data = [
// {
// id: 'msg-id',
// role: 'user', // or 'assistant'
// payloadJson: '{"role":"user","content":"Hello"}',
// createdAt: '2026-03-10T...',
// updatedAt: '2026-03-10T...',
// }
// ]
| Parameter | Type | Required | Description |
|---|---|---|---|
conversationId | string | Yes | Conversation UUID |
projects.list
Required capability: host.projects.read
List all imported projects.
var result = await window.chaton.extensionHostCall(EXT_ID, 'projects.list');
// result.data = [
// { id: 'uuid', name: 'my-project', repoPath: '/path/to/repo', updatedAt: '...' }
// ]
open.mainView
Open an extension's main view programmatically.
await window.chaton.extensionHostCall(EXT_ID, 'open.mainView', {
viewId: 'myext.main',
});
Channel-Specific Methods
For channels.upsertGlobalThread, channels.ingestMessage, channels.reportStatus, and channels.getStatus, see the Channels documentation.
Cross-Extension API Calls
extensionCall(callerExtensionId, targetExtensionId, apiName, versionRange, payload)
Call another extension's exposed API.
var result = await window.chaton.extensionCall(
EXT_ID, // Your extension ID (the caller)
'@other/chatons-weather', // Target extension ID
'get_weather', // API name (declared in target's apis.exposes)
'^1.0.0', // Semver version range
{ city: 'Paris' } // Payload
);
Exposing an API
In your manifest:
{
"apis": {
"exposes": [
{ "name": "notes.search", "version": "1.0.0" }
]
}
}
Handle incoming API calls via postMessage:
window.addEventListener('message', function (event) {
var data = event && event.data;
if (!data || data.type !== 'chaton.extension.apiCall') return;
if (data.apiName !== 'notes.search') return;
var results = doSearch(data.payload);
window.parent.postMessage({
type: 'chaton.extension.apiCallResponse',
callId: data.callId,
result: results,
}, '*');
});
Model Listing
listPiModels()
List all available AI models.
var result = await window.chaton.listPiModels();
// result = {
// ok: true,
// models: [
// { id: 'gpt-4o', provider: 'openai', key: 'openai/gpt-4o', scoped: true },
// { id: 'claude-3.5-sonnet', provider: 'anthropic', key: 'anthropic/claude-3.5-sonnet', scoped: false },
// ]
// }
The scoped field indicates whether the user has starred this model. The model picker widget (see UI Library) uses this to show starred models first.
Initial State
getInitialState()
Get the app's current state (projects, conversations, etc.).
var state = await window.chaton.getInitialState();
// state.projects -- array of project objects
// state.conversations -- array of conversation objects
This is called by built-in extensions such as automation, memory, and topbar widgets that need startup context.
LLM Tool Integration
LLM tools are exposed through the manifest and handled via the cross-extension API system. The flow:
- Declare the tool in
llm.tools[]andapis.exposes[]in your manifest - The AI model decides to call your tool
- Chatons routes it as an
apiCallmessage to your extension - You process the call and respond via
postMessage
Manifest
{
"capabilities": ["llm.tools"],
"llm": {
"tools": [
{
"name": "myext_search_notes",
"description": "Search through the user's saved notes.",
"promptSnippet": "Use myext_search_notes to find user notes by keyword.",
"promptGuidelines": [
"Always search before creating a duplicate note."
],
"parameters": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search keyword" }
},
"required": ["query"]
}
}
]
},
"apis": {
"exposes": [
{ "name": "myext_search_notes", "version": "1.0.0" }
]
}
}
Handler
window.addEventListener('message', function (event) {
var data = event && event.data;
if (!data || data.type !== 'chaton.extension.apiCall') return;
if (data.apiName !== 'myext_search_notes') return;
var query = (data.payload && data.payload.query) || '';
var results = notes.filter(function (n) {
return n.text.toLowerCase().includes(query.toLowerCase());
});
window.parent.postMessage({
type: 'chaton.extension.apiCallResponse',
callId: data.callId,
result: { notes: results, count: results.length },
}, '*');
});
Returning a requirement sheet
If your tool needs prerequisites (API key, auth), return an error with requirementSheet:
window.parent.postMessage({
type: 'chaton.extension.apiCallResponse',
callId: data.callId,
result: {
ok: false,
error: {
code: 'unauthorized',
message: 'API key not configured.',
requirementSheet: {
title: 'Configure API Key',
html: '<html>...</html>',
},
},
},
}, '*');
See Requirement Sheets for the full API.
Tool name rules
- Must match
^[a-zA-Z0-9_-]+$ - If your name contains
.or/, Chatons auto-normalizes it and logs a warning - The exposed API name and the tool name must match
Error Handling
All API calls return { ok: true, data: ... } on success or { ok: false, error: { code, message } } on failure.
Error codes
| Code | Meaning |
|---|---|
unauthorized | Missing capability or permission denied |
invalid_args | Bad parameter (missing required field, wrong type) |
not_found | Resource does not exist (file, conversation, etc.) |
rate_limited | Too many requests |
internal | Server-side error |
missing_api_key | API key not configured |
Recommended pattern
try {
var result = await window.chaton.extensionStorageKvGet(EXT_ID, 'key');
if (!result.ok) {
console.error('Storage error:', result.error.code, result.error.message);
return;
}
// Use result.data
} catch (err) {
console.error('Unexpected error:', err);
}
IPC Channel Names
For developers tracing behavior across Electron's main and renderer processes, here are the IPC channel names used by the extension system:
| IPC Channel | Maps To |
|---|---|
extensions:storage:kv:get | extensionStorageKvGet |
extensions:storage:kv:set | extensionStorageKvSet |
extensions:storage:kv:delete | extensionStorageKvDelete |
extensions:storage:kv:list | extensionStorageKvList |
extensions:storage:files:read | extensionStorageFilesRead |
extensions:storage:files:write | extensionStorageFilesWrite |
extensions:events:subscribe | extensionEventSubscribe |
extensions:events:publish | extensionEventPublish |
extensions:queue:enqueue | extensionQueueEnqueue |
extensions:queue:consume | extensionQueueConsume |
extensions:queue:ack | extensionQueueAck |
extensions:queue:nack | extensionQueueNack |
extensions:queue:deadLetter:list | extensionQueueDeadLetterList |
extensions:hostCall | extensionHostCall |
extensions:call | extensionCall |
extensions:runtime:health | extensionRuntimeHealth |
Capabilities Reference
Topbar UI entries declared in ui.topbarItems[] can trigger generic renderer actions via openMainView or deeplink, or render compact extension widgets via widget.viewId.
Widget iframes also receive a standard chaton.extension.topbarContext message whenever their host context changes.
| Capability | Required For |
|---|---|
ui.menu | ui.menuItems[] in manifest |
ui.mainView | ui.mainViews[] in manifest |
storage.kv | extensionStorageKvGet/Set/Delete/List |
storage.files | extensionStorageFilesRead/Write |
events.subscribe | extensionEventSubscribe |
events.publish | extensionEventPublish |
queue.publish | extensionQueueEnqueue |
queue.consume | extensionQueueConsume/Ack/Nack |
llm.tools | LLM tool exposure |
host.notifications | notifications.notify host call |
host.conversations.read | conversations.list, conversations.getMessages |
host.conversations.write | channels.* methods, conversation creation |
host.projects.read | projects.list |