Manifest Reference
Every Stina extension must include a manifest.json file at its root. This file declares the extension’s identity, required permissions, and everything it contributes to Stina. The manifest is validated at install time using a Zod schema defined in the @stina/extension-api package.
Top-Level Fields
Section titled “Top-Level Fields”Required Fields
Section titled “Required Fields”| Field | Type | Description |
|---|---|---|
id | string | Unique extension identifier. Must be lowercase alphanumeric characters and hyphens only (regex: ^[a-z0-9-]+$). Example: "my-extension" |
name | string | Human-readable display name shown in the UI. Must be non-empty. Example: "My Extension" |
version | string | Semver version string. Must match the pattern X.Y.Z. Example: "1.0.0" |
description | string | Short description of what the extension does. Must be non-empty. |
author | object | Author information. See Author Object. |
main | string | Entry point file, relative to the extension root. This is the JavaScript file loaded when the extension is activated. Example: "index.js" |
permissions | string[] | List of required permissions. The user is shown these at install time. See Permissions. |
Author Object
Section titled “Author Object”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Author or organization name. Must be non-empty. |
url | string | No | Author URL. Must be a valid URL if provided. |
{ "author": { "name": "Stina Team", "url": "https://github.com/einord" }}Optional Fields
Section titled “Optional Fields”| Field | Type | Default | Description |
|---|---|---|---|
$schema | string | — | JSON Schema URL for IDE validation and autocompletion. Use "https://stina.app/schemas/extension-manifest.json". |
type | "provider" | "tools" | — | Extension type. Use "provider" for AI model providers and "tools" for feature extensions. Used for categorization in the extension registry. |
repository | string | — | GitHub repository URL. Must be a valid URL. Example: "https://github.com/einord/stina-ext-mail" |
license | string | — | SPDX license identifier. Example: "MIT" |
engines | object | — | Engine compatibility requirements. See Engines. |
platforms | ("web" | "electron" | "tui")[] | All platforms | Restrict the extension to specific platforms. If omitted, the extension is available on all platforms. |
contributes | object | — | Declares what the extension adds to Stina. See Contributes. |
Engines
Section titled “Engines”The engines field specifies minimum version requirements. Currently only stina is supported.
{ "engines": { "stina": ">=0.5.0" }}| Field | Type | Description |
|---|---|---|
stina | string | Minimum Stina version required. Uses semver range syntax. |
Permissions
Section titled “Permissions”The permissions array declares what capabilities the extension needs. Users see these at install time and can decide whether to trust the extension.
Network Permissions
Section titled “Network Permissions”| Permission | Description |
|---|---|
network:* | Unrestricted network access to any host |
network:localhost | Access to localhost only |
network:localhost:<port> | Access to a specific localhost port (e.g., network:localhost:11434) |
network:<host> | Access to a specific host (e.g., network:api.openai.com) |
Storage Permissions
Section titled “Storage Permissions”| Permission | Description |
|---|---|
storage.collections | Create and use document collections for persistent data |
secrets.manage | Store and retrieve secrets (API keys, tokens, passwords) |
User Data Permissions
Section titled “User Data Permissions”| Permission | Description |
|---|---|
user.profile.read | Read the user’s profile information (name, locale) |
user.location.read | Read the user’s location |
chat.history.read | Read past chat conversations |
chat.current.read | Read the current chat conversation |
Capability Permissions
Section titled “Capability Permissions”| Permission | Description |
|---|---|
provider.register | Register as an AI model provider |
tools.register | Register tools the AI can call |
actions.register | Register actions (internal functions callable from UI components) |
settings.register | Register user-configurable settings |
commands.register | Register slash commands |
panels.register | Register right-side panel views |
events.emit | Emit custom events |
scheduler.register | Register scheduled/recurring tasks |
chat.message.write | Send messages to the chat programmatically |
background.workers | Run background worker threads |
System Permissions
Section titled “System Permissions”| Permission | Description |
|---|---|
files.read | Read files from the local filesystem |
files.write | Write files to the local filesystem |
clipboard.read | Read from the system clipboard |
clipboard.write | Write to the system clipboard |
Contributes
Section titled “Contributes”The contributes field declares what the extension adds to Stina. All sub-fields are optional. You only include the sections relevant to your extension.
{ "contributes": { "providers": [], "tools": [], "commands": [], "settings": [], "toolSettings": [], "panels": [], "storage": {}, "prompts": [] }}contributes.providers
Section titled “contributes.providers”Type: ProviderDefinition[]
For AI provider extensions. Each entry declares a provider that can serve AI models. Provider extensions typically have type: "provider" and require the provider.register permission.
ProviderDefinition
Section titled “ProviderDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique provider identifier within this extension. |
name | string | Yes | Display name shown in the provider selection UI. |
description | string | No | Short description of the provider. |
suggestedDefaultModel | string | No | Model name to suggest when the user first configures this provider. |
defaultSettings | Record<string, unknown> | No | Default values for provider settings (e.g., { "url": "http://localhost:11434" }). |
configSchema | ProviderConfigSchema | No | Schema for the provider-specific configuration UI. See Provider Config Schema. |
Provider Config Schema
Section titled “Provider Config Schema”The configSchema generates a settings form in the UI for configuring the provider.
| Field | Type | Required | Description |
|---|---|---|---|
properties | Record<string, ProviderConfigProperty> | Yes | Property definitions, keyed by setting name. |
order | string[] | No | Display order of properties. Defaults to object key order. |
ProviderConfigProperty
Section titled “ProviderConfigProperty”| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | UI control type. One of: "string", "number", "boolean", "select", "password", "url". |
title | string | Yes | Display label for the field. |
description | string | No | Help text shown below the input. |
default | unknown | No | Default value. |
required | boolean | No | Whether the field must be filled. |
placeholder | string | No | Placeholder text for input fields. |
options | { value: string, label: string }[] | No | For select type: the available options. |
validation | ProviderConfigValidation | No | Validation rules. See below. |
Property types:
string— Free-text input field.number— Numeric input field.boolean— Toggle/checkbox.select— Dropdown with predefined options.password— Masked input field (value stored securely).url— URL input field with URL validation.
ProviderConfigValidation:
| Field | Type | Description |
|---|---|---|
pattern | string | Regex pattern the value must match. |
minLength | number | Minimum string length. |
maxLength | number | Maximum string length. |
min | number | Minimum numeric value. |
max | number | Maximum numeric value. |
Example: AI provider
Section titled “Example: AI provider”{ "providers": [ { "id": "ollama", "name": "Ollama", "description": "Local AI models via Ollama", "suggestedDefaultModel": "llama3.2:8b", "defaultSettings": { "url": "http://localhost:11434", "thinking": "off" }, "configSchema": { "order": ["url", "thinking"], "properties": { "url": { "type": "url", "title": "Server URL", "description": "URL to your Ollama server", "placeholder": "http://localhost:11434", "default": "http://localhost:11434", "required": true }, "thinking": { "type": "select", "title": "Thinking Mode", "description": "Enable thinking/reasoning for supported models", "default": "off", "options": [ { "value": "off", "label": "Off" }, { "value": "on", "label": "On" }, { "value": "medium", "label": "Medium (extended)" } ] } } } } ]}contributes.tools
Section titled “contributes.tools”Type: ToolDefinition[]
Tools the AI can call during conversations. Each tool is invoked by Stina when the AI decides to use it. The actual implementation is registered in your extension’s code; the manifest only declares metadata.
Requires the tools.register permission.
ToolDefinition
Section titled “ToolDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique tool identifier within this extension. Used in code to match the tool handler. |
name | LocalizedString | Yes | Display name. Supports localization. |
description | LocalizedString | Yes | Description of what the tool does. The AI receives the English text (or fallback) to decide when to use the tool. Supports localization. |
parameters | Record<string, unknown> | No | JSON Schema describing the tool’s parameters. |
confirmation | ToolConfirmationConfig | No | If set, user must confirm before the tool runs. |
ToolConfirmationConfig
Section titled “ToolConfirmationConfig”| Field | Type | Required | Description |
|---|---|---|---|
prompt | LocalizedString | No | Custom confirmation prompt shown to the user. If omitted, a generic prompt like “Allow {toolName} to run?” is used. |
Example
Section titled “Example”{ "tools": [ { "id": "mail_list_recent", "name": "List Recent Emails", "description": "List recent emails from configured accounts." }, { "id": "mail_send", "name": { "en": "Send Email", "sv": "Skicka e-post" }, "description": { "en": "Send an email", "sv": "Skicka ett e-postmeddelande" }, "confirmation": { "prompt": { "en": "Allow sending this email?", "sv": "Tillåt att skicka detta e-postmeddelande?" } } } ]}contributes.commands
Section titled “contributes.commands”Type: CommandDefinition[]
Slash commands that users can type in the chat input (e.g., /weather). Requires the commands.register permission.
CommandDefinition
Section titled “CommandDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Command identifier. This is what follows the / in the chat. For example, "weather" creates the /weather command. |
name | string | Yes | Display name shown in the command palette/autocomplete. |
description | string | Yes | Description shown alongside the command in the UI. |
Example
Section titled “Example”{ "commands": [ { "id": "weather", "name": "/weather", "description": "Get weather forecast for your location" } ]}contributes.settings
Section titled “contributes.settings”Type: SettingDefinition[]
User-configurable settings that appear in the extension’s settings page. Requires the settings.register permission.
SettingDefinition
Section titled “SettingDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Setting identifier. Automatically namespaced by the extension ID. |
title | string | Yes | Display label in the settings UI. |
description | string | No | Help text shown below the input. |
type | string | Yes | Setting type. One of: "string", "number", "boolean", "select". |
default | unknown | No | Default value. |
options | { value: string, label: string }[] | No | For select type: static list of options. |
optionsToolId | string | No | For select type: tool ID that returns dynamic options. |
optionsParams | Record<string, unknown> | No | Parameters to pass to the options tool. |
optionsMapping | SettingOptionsMapping | No | Mapping from the options tool response to option values/labels. |
createToolId | string | No | For select type: tool ID for creating a new option inline. |
createLabel | string | No | Label for the create action button. |
createFields | SettingDefinition[] | No | Fields shown in the inline create form. Recursive — uses the same SettingDefinition structure. |
createParams | Record<string, unknown> | No | Static parameters always sent to the create tool. |
createMapping | SettingCreateMapping | No | Mapping from the create tool response to the new option value. |
validation | object | No | Validation rules. See below. |
Setting types:
string— Free-text input field.number— Numeric input field.boolean— Toggle/checkbox.select— Dropdown. Must provide eitheroptions(static) oroptionsToolId(dynamic).
Validation rules:
| Field | Type | Description |
|---|---|---|
required | boolean | Whether the field must be filled. |
min | number | Minimum value (for numbers) or minimum length (for strings). |
max | number | Maximum value (for numbers) or maximum length (for strings). |
pattern | string | Regex pattern the value must match. |
SettingOptionsMapping (for optionsToolId):
| Field | Type | Required | Description |
|---|---|---|---|
itemsKey | string | Yes | Key for the items array in the tool result data. |
valueKey | string | Yes | Key for the option value within each item. |
labelKey | string | Yes | Key for the option display label within each item. |
descriptionKey | string | No | Key for an optional description within each item. |
SettingCreateMapping (for createToolId):
| Field | Type | Required | Description |
|---|---|---|---|
resultKey | string | No | Key for the result data object in the tool response. |
valueKey | string | Yes | Key for the new option’s value (defaults to "id"). |
Example
Section titled “Example”{ "settings": [ { "id": "gmail_client_id", "title": "Gmail Client ID", "description": "OAuth2 Client ID from Google Cloud Console for Gmail access", "type": "string" }, { "id": "outlook_tenant_id", "title": "Outlook Tenant ID", "description": "Azure AD Tenant ID (use 'common' for multi-tenant)", "type": "string", "default": "common" } ]}contributes.toolSettings
Section titled “contributes.toolSettings”Type: ToolSettingsViewDefinition[]
Declarative UI views for managing extension-specific data. These appear as tabs in the extension’s settings page and can display lists, forms, or custom component layouts. There are two view kinds: list and component.
Requires the actions.register permission (for component views) or tools.register (for list views).
ToolSettingsViewDefinition
Section titled “ToolSettingsViewDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique view identifier within the extension. |
title | string | Yes | Display title shown as the tab/section label. |
description | string | No | Help text describing the view’s purpose. |
view | ToolSettingsView | Yes | View configuration. Either a list view or a component view. |
fields | SettingDefinition[] | No | Field definitions for create/edit forms. Uses the same SettingDefinition structure as contributes.settings. Primarily used with list views to define the add/edit form fields. |
List View
Section titled “List View”A list view displays items from a tool-backed data source with built-in search, pagination, and CRUD operations.
{ "view": { "kind": "list", "listToolId": "people_list", "getToolId": "people_get", "upsertToolId": "people_upsert", "deleteToolId": "people_delete", "mapping": { "itemsKey": "people", "countKey": "count", "idKey": "id", "labelKey": "name", "descriptionKey": "description", "secondaryKey": "relationship" }, "searchParam": "query", "limitParam": "limit", "idParam": "id" }}List view fields:
| Field | Type | Required | Description |
|---|---|---|---|
kind | "list" | Yes | Must be "list". |
listToolId | string | Yes | Tool ID called to list items. |
getToolId | string | No | Tool ID called to fetch a single item’s details (for edit forms). |
upsertToolId | string | No | Tool ID called to create or update an item. |
deleteToolId | string | No | Tool ID called to delete an item. |
mapping | ToolSettingsListMapping | Yes | Maps tool response data to UI fields. See below. |
searchParam | string | No | Parameter name sent to the list tool for search queries. Default: "query". |
limitParam | string | No | Parameter name sent to the list tool for pagination limit. Default: "limit". |
idParam | string | No | Parameter name sent to get/delete tools for the item ID. Default: "id". |
listParams | Record<string, unknown> | No | Static parameters always sent to the list tool. |
ToolSettingsListMapping:
| Field | Type | Required | Description |
|---|---|---|---|
itemsKey | string | Yes | Key for the items array in the tool’s result data. |
countKey | string | No | Key for the total count in the tool’s result data (for pagination). |
idKey | string | Yes | Key for the item’s unique identifier. |
labelKey | string | Yes | Key for the item’s primary display label. |
descriptionKey | string | No | Key for the item’s description text. |
secondaryKey | string | No | Key for a secondary label displayed below or beside the primary. |
Component View
Section titled “Component View”A component view uses the declarative component DSL to render custom UI. Data is fetched via actions and made available as scope variables.
{ "view": { "kind": "component", "data": { "settings": { "action": "getSettings", "refreshOn": ["mail.settings.changed"] } }, "content": { "component": "VerticalStack", "gap": 1, "children": [ { "component": "TextInput", "label": "Instruction", "value": "$settings.instruction", "onChangeAction": { "action": "updateSetting", "params": { "key": "instruction", "value": "$value" } } } ] } }}Component view fields:
| Field | Type | Required | Description |
|---|---|---|---|
kind | "component" | Yes | Must be "component". |
data | Record<string, ActionDataSource> | No | Data sources. Each key becomes a scope variable accessible in the component tree as $keyName. |
content | ExtensionComponentData | Yes | Root component to render. Uses the declarative component DSL. |
ActionDataSource:
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Action ID to call for fetching data. Must be registered by the extension’s code. |
params | Record<string, unknown> | No | Static parameters to pass to the action. |
refreshOn | string[] | No | Event names that trigger an automatic refresh of this data source. |
contributes.panels
Section titled “contributes.panels”Type: PanelDefinition[]
Right-side panel views that display persistent, auto-refreshing UI alongside the chat. Panels use the same component view system as tool settings. Requires the panels.register permission.
PanelDefinition
Section titled “PanelDefinition”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique panel identifier within the extension. |
title | string | Yes | Display title shown in the panel header and panel selector. |
icon | string | No | Icon name (from the Huge Icons set). Displayed in the panel selector. |
view | PanelView | Yes | Panel view schema. Uses the same component view structure as tool settings. |
The view field supports kind: "component" with the same data and content structure described in Component View.
Example
Section titled “Example”{ "panels": [ { "id": "work.todos", "title": "Work", "icon": "check-list", "view": { "kind": "component", "data": { "groups": { "action": "getGroups", "refreshOn": ["work.todo.changed", "work.project.changed"] } }, "content": { "component": "VerticalStack", "gap": 1, "children": { "each": "$groups", "as": "group", "items": [ { "component": "Panel", "title": "$group.title", "content": { "component": "Label", "text": "$group.summary" } } ] } } } } ]}contributes.storage
Section titled “contributes.storage”Type: object
Declares document collections the extension needs for persistent data storage. Requires the storage.collections permission.
Collections are schemaless document stores. You declare the collection names and which fields should be indexed for fast queries.
| Field | Type | Required | Description |
|---|---|---|---|
collections | Record<string, CollectionConfig> | Yes | Collection definitions, keyed by collection name. |
CollectionConfig:
| Field | Type | Required | Description |
|---|---|---|---|
indexes | string[] | No | Field names to index for efficient querying. |
Example
Section titled “Example”{ "storage": { "collections": { "accounts": { "indexes": ["email", "provider", "enabled"] }, "settings": {}, "processed": { "indexes": ["accountId", "messageId"] } } }}contributes.prompts
Section titled “contributes.prompts”Type: PromptContribution[]
System prompt contributions that are injected into the AI’s system prompt. This is how extensions tell the AI about available tools and how to use them.
PromptContribution
Section titled “PromptContribution”| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier within the extension. |
title | string | No | Optional title for the prompt section. |
section | string | No | Where in the system prompt to place this text. One of: "system", "behavior", "tools". See below. |
text | string | No | Plain text prompt content. Use this for single-language prompts. |
i18n | Record<string, string> | No | Localized prompt content, keyed by locale code (e.g., "en", "sv"). The AI receives the prompt in the user’s preferred language. |
order | number | No | Ordering hint within the section. Lower values come first. |
You must provide either text or i18n (or both). If both are provided, i18n takes precedence when a matching locale is available.
Prompt sections:
"system"— Core system-level instructions. Use sparingly."behavior"— Behavioral guidelines for the AI (tone, style, rules)."tools"— Instructions about available tools and how/when to use them. This is the most common section for extensions.
Example
Section titled “Example”{ "prompts": [ { "id": "mail-reader-instructions", "section": "tools", "i18n": { "en": "You have access to Mail Reader tools for monitoring incoming emails. Use mail_accounts_list to see configured accounts. Use mail_list_recent to get recent emails.", "sv": "Du har tillgang till Mail Reader-verktyg for att overvaka inkommande e-post. Anvand mail_accounts_list for att se konfigurerade konton." } } ]}Localized Strings
Section titled “Localized Strings”Many text fields in the manifest support the LocalizedString type. A LocalizedString is either a plain string or an object mapping language codes to translations.
// Simple string -- used as-is regardless of locale"Get Weather"
// Localized -- Stina selects the best match for the user's language{ "en": "Get Weather", "sv": "Hamta vader", "de": "Wetter abrufen" }Resolution order: preferred locale -> "en" (fallback) -> first available value -> empty string.
Fields that support LocalizedString:
contributes.tools[].namecontributes.tools[].descriptioncontributes.tools[].confirmation.prompt
Other text fields (such as name, description at the top level, and panel/command labels) are plain strings.
Complete Example
Section titled “Complete Example”Below is a full manifest.json for a hypothetical tool extension that manages bookmarks. It demonstrates multiple contribution types working together.
{ "$schema": "https://stina.app/schemas/extension-manifest.json", "id": "bookmarks", "name": "Bookmarks", "version": "1.0.0", "description": "Save and organize bookmarks from conversations.", "type": "tools", "author": { "name": "Stina Team", "url": "https://github.com/einord" }, "repository": "https://github.com/einord/stina-ext-bookmarks", "license": "MIT", "engines": { "stina": ">=0.5.0" }, "platforms": ["web", "electron", "tui"], "main": "index.js",
"permissions": [ "tools.register", "actions.register", "panels.register", "storage.collections", "events.emit" ],
"contributes": { "storage": { "collections": { "bookmarks": { "indexes": ["title", "category", "createdAt"] }, "categories": { "indexes": ["name"] } } },
"tools": [ { "id": "bookmarks_list", "name": "List Bookmarks", "description": "List saved bookmarks with optional filtering." }, { "id": "bookmarks_add", "name": { "en": "Add Bookmark", "sv": "Lagg till bokmarke" }, "description": { "en": "Save a new bookmark.", "sv": "Spara ett nytt bokmarke." } }, { "id": "bookmarks_delete", "name": "Delete Bookmark", "description": "Delete a bookmark by ID.", "confirmation": { "prompt": "Allow deleting this bookmark?" } } ],
"commands": [ { "id": "bookmark", "name": "/bookmark", "description": "Save the current topic as a bookmark" } ],
"settings": [ { "id": "default_category", "title": "Default Category", "description": "Category assigned to new bookmarks when none is specified", "type": "string", "default": "Uncategorized" } ],
"toolSettings": [ { "id": "bookmarks", "title": "Bookmarks", "description": "Manage saved bookmarks.", "view": { "kind": "list", "listToolId": "bookmarks_list", "getToolId": "bookmarks_get", "upsertToolId": "bookmarks_upsert", "deleteToolId": "bookmarks_delete", "mapping": { "itemsKey": "bookmarks", "countKey": "count", "idKey": "id", "labelKey": "title", "descriptionKey": "url" }, "searchParam": "query", "limitParam": "limit", "idParam": "id" }, "fields": [ { "id": "title", "title": "Title", "type": "string", "validation": { "required": true } }, { "id": "url", "title": "URL", "type": "string", "validation": { "required": true } }, { "id": "category", "title": "Category", "type": "string" } ] } ],
"panels": [ { "id": "bookmarks-panel", "title": "Bookmarks", "icon": "bookmark-02", "view": { "kind": "component", "data": { "bookmarks": { "action": "getBookmarks", "refreshOn": ["bookmarks.changed"] } }, "content": { "component": "VerticalStack", "gap": 0.5, "children": { "each": "$bookmarks", "as": "item", "items": [ { "component": "HorizontalStack", "gap": 0.5, "children": [ { "component": "Label", "text": "$item.title" }, { "component": "Pill", "text": "$item.category", "variant": "info" } ] } ] } } } } ],
"prompts": [ { "id": "bookmarks-instructions", "section": "tools", "i18n": { "en": "You have access to Bookmarks tools. Use bookmarks_add to save interesting links or topics the user mentions. Use bookmarks_list to recall saved bookmarks.", "sv": "Du har tillgang till Bookmarks-verktyg. Anvand bookmarks_add for att spara intressanta lankar eller amnen som anvandaren namner. Anvand bookmarks_list for att hamta sparade bokmarken." } } ] }}