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:


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

TopicWhen Fired
app.startedApp startup complete
conversation.createdNew conversation created
conversation.updatedConversation properties changed
conversation.message.receivedNew message in a conversation
conversation.agent.startedAI model started processing
conversation.agent.endedAI model finished processing
conversation.turn.endedFull turn (user + assistant) completed
conversation.tool.executedA tool call was executed
project.createdNew project imported
project.deletedProject removed
extension.installedAn extension was installed
extension.enabledAn 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

StateMeaning
queuedWaiting to be consumed
processingCurrently being processed by a consumer
doneSuccessfully acknowledged
deadFailed 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!',
});
ParameterTypeRequiredDescription
titlestringNoNotification title. Defaults to "Notification".
bodystringNoNotification 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...',
//   }
// ]
ParameterTypeRequiredDescription
conversationIdstringYesConversation 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:

  1. Declare the tool in llm.tools[] and apis.exposes[] in your manifest
  2. The AI model decides to call your tool
  3. Chatons routes it as an apiCall message to your extension
  4. 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

CodeMeaning
unauthorizedMissing capability or permission denied
invalid_argsBad parameter (missing required field, wrong type)
not_foundResource does not exist (file, conversation, etc.)
rate_limitedToo many requests
internalServer-side error
missing_api_keyAPI key not configured
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 ChannelMaps To
extensions:storage:kv:getextensionStorageKvGet
extensions:storage:kv:setextensionStorageKvSet
extensions:storage:kv:deleteextensionStorageKvDelete
extensions:storage:kv:listextensionStorageKvList
extensions:storage:files:readextensionStorageFilesRead
extensions:storage:files:writeextensionStorageFilesWrite
extensions:events:subscribeextensionEventSubscribe
extensions:events:publishextensionEventPublish
extensions:queue:enqueueextensionQueueEnqueue
extensions:queue:consumeextensionQueueConsume
extensions:queue:ackextensionQueueAck
extensions:queue:nackextensionQueueNack
extensions:queue:deadLetter:listextensionQueueDeadLetterList
extensions:hostCallextensionHostCall
extensions:callextensionCall
extensions:runtime:healthextensionRuntimeHealth

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.

CapabilityRequired For
ui.menuui.menuItems[] in manifest
ui.mainViewui.mainViews[] in manifest
storage.kvextensionStorageKvGet/Set/Delete/List
storage.filesextensionStorageFilesRead/Write
events.subscribeextensionEventSubscribe
events.publishextensionEventPublish
queue.publishextensionQueueEnqueue
queue.consumeextensionQueueConsume/Ack/Nack
llm.toolsLLM tool exposure
host.notificationsnotifications.notify host call
host.conversations.readconversations.list, conversations.getMessages
host.conversations.writechannels.* methods, conversation creation
host.projects.readprojects.list

On this page