Skip to content

Getting Started

Extensions let you add new capabilities to Stina — from connecting AI providers to giving the assistant entirely new skills. This guide walks you through creating your first extension from an empty folder to a working build.

A Stina extension is a standalone TypeScript project that builds against the @stina/extension-api package on npm. Extensions run inside Stina’s extension host and interact with the application through a well-defined API.

There are two types of extensions:

  • Provider extensions connect Stina to AI models. Examples include the Ollama extension (local models) and the OpenAI extension (cloud API).
  • Tool extensions add features that the AI can use during conversations, such as reading emails, managing todos, or remembering people.

Every extension has an activate function that Stina calls on startup, a manifest.json that declares what it contributes, and a build step that produces a single JavaScript bundle.

Before you start, make sure you have:

  • Node.js 18 or later
  • pnpm package manager
  • Basic TypeScript knowledge
  • A running Stina instance (desktop app, web, or from source) to test your extension

Create a new directory and initialize it:

Terminal window
mkdir my-extension && cd my-extension
pnpm init
pnpm add -D @stina/extension-api tsup typescript

This installs the three dependencies you need:

PackagePurpose
@stina/extension-apiType definitions and contracts for the extension API
tsupFast TypeScript bundler built on esbuild
typescriptTypeScript compiler for type checking

A minimal extension looks like this:

my-extension/
src/
index.ts # Entry point (activate/deactivate)
manifest.json # Extension metadata and declarations
tsup.config.ts # Build configuration
package.json

The following sections walk through each file.

The manifest tells Stina everything it needs to know about your extension — its identity, what permissions it needs, and what it contributes.

Create manifest.json in the project root:

{
"id": "my-extension",
"name": "My Extension",
"version": "1.0.0",
"description": "A simple Stina extension",
"author": { "name": "Your Name" },
"type": "tools",
"main": "index.js",
"permissions": ["tools.register"],
"engines": { "stina": ">=0.5.0" },
"contributes": {
"tools": [
{
"id": "hello",
"name": "Hello World",
"description": "A simple greeting tool"
}
]
}
}

Key fields:

  • id — A unique identifier for your extension. Use lowercase with hyphens.
  • type — Either "tools" or "provider".
  • main — The built entry point file (relative to the build output).
  • permissions — The APIs your extension needs access to. Stina only exposes the APIs you declare here.
  • contributes.tools — Declares the tools your extension will register. Each tool listed here must be registered in code with a matching id.

This is your extension’s entry point. Stina calls the activate function when the extension loads.

Create src/index.ts:

import type { ExtensionModule, ExtensionContext } from '@stina/extension-api'
const extension: ExtensionModule = {
activate(context: ExtensionContext) {
context.tools?.register({
id: 'hello',
name: 'Hello World',
description: 'A simple greeting tool',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name to greet' }
}
},
async execute(params) {
const name = (params.name as string) || 'World'
return { success: true, data: `Hello, ${name}!` }
}
})
}
}
export default extension

A few things to note:

  • activate is called once when Stina loads the extension. This is where you register all your tools, providers, and actions.
  • context.tools is only available if you declared the tools.register permission in the manifest. It will be undefined otherwise, which is why the optional chaining (?.) is used.
  • parameters uses JSON Schema to describe the input the AI should provide when calling your tool.
  • execute receives the parameters from the AI and returns a ToolResult with success, data, and optionally a message or error. It also receives an ExecutionContext as a second argument, which provides access to storage, secrets, and request metadata.
  • The activate function can return a Disposable (with a dispose() method) for cleanup. Alternatively, you can define an optional deactivate method on the extension module for teardown logic.

The full signature of a tool’s execute method is:

async execute(params: Record<string, unknown>, context: ExecutionContext): Promise<ToolResult>

The ExecutionContext gives you access to per-request information:

async execute(params, context) {
// Access user-scoped storage
const prefs = await context.userStorage.get('preferences', 'theme')
// Access extension-scoped secrets
const apiKey = await context.secrets.get('api-key')
// Extension metadata
console.log(`Running ${context.extension.id} v${context.extension.version}`)
return { success: true, data: 'Done' }
}

Create tsup.config.ts in the project root:

import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: false,
clean: true,
external: ['@stina/extension-api']
})

The important part is external: ['@stina/extension-api'] — this tells the bundler not to include the extension API in your bundle. Stina provides it at runtime.

Then add build scripts to your package.json:

{
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
}
}

Build your extension:

Terminal window
pnpm build

This produces a dist/index.js file. To test it in Stina:

  1. Open Stina
  2. Go to Settings (gear icon)
  3. Navigate to Extensions
  4. Click Install from folder
  5. Select your extension’s project directory

Stina will read your manifest.json, load the built bundle, and call activate. Once loaded, the AI will be able to use your “Hello World” tool in conversations.

During development, use watch mode for faster iteration:

Terminal window
pnpm dev

This rebuilds automatically whenever you save a file. You may need to reload the extension in Stina after each rebuild.

Now that you have a working extension, explore the rest of the documentation to build more advanced functionality:

  • Manifest Reference — All manifest fields explained in detail
  • Extension API — Full API documentation for tools, providers, storage, and more
  • Permissions — Available permissions and what they unlock
  • UI Components — Build settings panels and custom UIs for your extension
  • Publishing — Package and distribute your extension to other Stina users