@janhq/core package provides all the APIs, types, and interfaces needed to build Jan extensions.
Installation
yarn add @janhq/core
Import
import * as core from '@janhq/core'
Base Classes
BaseExtension
All extensions extend fromBaseExtension:
abstract class BaseExtension implements ExtensionType {
name: string
productName?: string
url: string
active?: boolean
description?: string
version?: string
abstract onLoad(): void
abstract onUnload(): void
type(): ExtensionTypeEnum | undefined
compatibility(): Compatibility | undefined
// Settings management
async registerSettings(settings: SettingComponentProps[]): Promise<void>
async getSettings(): Promise<SettingComponentProps[]>
async getSetting<T>(key: string, defaultValue: T): Promise<T>
async updateSettings(componentProps: Partial<SettingComponentProps>[]): Promise<void>
onSettingUpdate<T>(key: string, value: T): void
// Model management
async registerModels(models: Model[]): Promise<void>
// Installation
async install(): Promise<void>
}
Key Methods
Called when the extension is loaded. Initialize your extension here.
Called when the extension is unloaded. Clean up resources here.
Returns the extension type. Return
undefined for generic extensions.Register settings for your extension that appear in Jan’s UI.
Get a setting value by key. Returns
defaultValue if not found.Update one or more settings programmatically.
Called when a setting is updated by the user. Override to react to changes.
Register models that can be used in Jan.
InferenceExtension
ExtendsBaseExtension for inference implementations:
abstract class InferenceExtension extends BaseExtension implements InferenceInterface {
type(): ExtensionTypeEnum {
return ExtensionTypeEnum.Inference
}
abstract inference(data: MessageRequest): Promise<ThreadMessage>
}
Perform inference on a message request and return the response.
ConversationalExtension
ExtendsBaseExtension for conversation management:
abstract class ConversationalExtension extends BaseExtension {
type(): ExtensionTypeEnum {
return ExtensionTypeEnum.Conversational
}
abstract listThreads(): Promise<Thread[]>
abstract createThread(thread: Partial<Thread>): Promise<Thread>
abstract modifyThread(thread: Thread): Promise<void>
abstract deleteThread(threadId: string): Promise<void>
abstract createMessage(message: Partial<ThreadMessage>): Promise<ThreadMessage>
abstract modifyMessage(message: ThreadMessage): Promise<ThreadMessage>
abstract deleteMessage(threadId: string, messageId: string): Promise<void>
abstract listMessages(threadId: string): Promise<ThreadMessage[]>
abstract getThreadAssistant(threadId: string): Promise<ThreadAssistantInfo>
abstract createThreadAssistant(threadId: string, assistant: ThreadAssistantInfo): Promise<ThreadAssistantInfo>
abstract modifyThreadAssistant(threadId: string, assistant: ThreadAssistantInfo): Promise<ThreadAssistantInfo>
}
AssistantExtension
ExtendsBaseExtension for assistant management:
abstract class AssistantExtension extends BaseExtension implements AssistantInterface {
type(): ExtensionTypeEnum {
return ExtensionTypeEnum.Assistant
}
abstract createAssistant(assistant: Assistant): Promise<void>
abstract deleteAssistant(assistant: Assistant): Promise<void>
abstract getAssistants(): Promise<Assistant[]>
}
Extension Types
enum ExtensionTypeEnum {
Assistant = 'assistant',
Conversational = 'conversational',
Inference = 'inference',
Model = 'model',
SystemMonitoring = 'systemMonitoring',
MCP = 'mcp',
HuggingFace = 'huggingFace',
Engine = 'engine',
Hardware = 'hardware',
RAG = 'rag',
VectorDB = 'vectorDB'
}
Event System
The event system allows extensions to communicate:const events = {
on: (eventName: string, handler: Function) => void,
off: (eventName: string, handler: Function) => void,
emit: (eventName: string, object: any) => void
}
Message Events
enum MessageEvent {
OnMessageSent = 'OnMessageSent',
OnMessageResponse = 'OnMessageResponse',
OnMessageUpdate = 'OnMessageUpdate'
}
import { events, MessageEvent } from '@janhq/core'
events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => {
console.log('User sent:', data)
})
events.emit(MessageEvent.OnMessageResponse, responseMessage)
Inference Events
enum InferenceEvent {
OnInferenceStopped = 'OnInferenceStopped'
}
Core Types
Model
type Model = {
object: string // "model"
version: string // Model version
format: string // "gguf", "safetensors", etc.
sources: ModelArtifact[] // Download sources
id: string // Unique identifier
model?: string // Model identifier (modern)
name: string // Human-readable name
created: number // Unix timestamp
description: string // Model description
settings: ModelSettingParams
parameters: ModelRuntimeParams
metadata: ModelMetadata
engine: string // "llamacpp", "openai", etc.
}
type ModelArtifact = {
filename: string
url: string
}
type ModelMetadata = {
author: string
tags: string[]
size: number // Size in bytes
cover?: string // Cover image URL
default_ctx_len?: number
default_max_tokens?: number
}
type ModelSettingParams = {
ctx_len?: number
ngl?: number
embedding?: boolean
n_parallel?: number
cpu_threads?: number
prompt_template?: string
system_prompt?: string
temperature?: number
top_p?: number
top_k?: number
// ... more parameters
}
type ModelRuntimeParams = {
temperature?: number
max_temperature?: number
token_limit?: number
top_k?: number
top_p?: number
stream?: boolean
max_tokens?: number
stop?: string[]
frequency_penalty?: number
presence_penalty?: number
engine?: string
}
Thread
type Thread = {
id: string // Unique identifier (ULID)
object: string // "thread"
title: string // Thread title
assistants: ThreadAssistantInfo[]
created: number // Unix timestamp
updated: number // Unix timestamp
metadata?: Record<string, unknown>
}
type ThreadAssistantInfo = {
id: string
name: string
model: ModelInfo
instructions?: string
tools?: AssistantTool[]
}
ThreadMessage
type ThreadMessage = {
id: string // Unique identifier (ULID)
object: string // "thread.message"
thread_id: string
assistant_id?: string
attachments?: Array<Attachment> | null
role: ChatCompletionRole // 'user', 'assistant', 'system', 'tool'
content: ThreadContent[]
status: MessageStatus // 'ready', 'pending', 'error', 'stopped'
created_at: number // Unix timestamp
completed_at: number // Unix timestamp
metadata?: Record<string, unknown>
type?: string
error_code?: ErrorCode
tool_call_id?: string
}
enum MessageStatus {
Ready = 'ready',
Pending = 'pending',
Error = 'error',
Stopped = 'stopped'
}
enum ChatCompletionRole {
System = 'system',
Assistant = 'assistant',
User = 'user',
Tool = 'tool'
}
ThreadContent
type ThreadContent = {
type: ContentType
// For text and reasoning content
text?: ContentValue
// For image content
image_url?: ImageContentValue
// For tool call content
tool_call_id?: string
tool_name?: string
input?: unknown
output?: unknown
}
enum ContentType {
Text = 'text',
Reasoning = 'reasoning',
Image = 'image_url',
ToolCall = 'tool_call'
}
type ContentValue = {
value: string
annotations: string[]
}
type ImageContentValue = {
detail?: string
url?: string
}
MessageRequest
type MessageRequest = {
id?: string
threadId: string // Thread ID (deprecated, use thread)
assistantId?: string
attachments: Array<Attachment> | null
messages?: ChatCompletionMessage[]
model?: ModelInfo
thread?: Thread
tools?: MessageTool[]
engine?: string
type?: string
}
type ChatCompletionMessage = {
content?: string | ChatCompletionMessageContent[]
role: ChatCompletionRole
type?: string
output?: string
tool_call_id?: string
}
Assistant
type Assistant = {
avatar: string
thread_location: string | undefined
id: string
object: string // "assistant"
created_at: number // Unix timestamp
name: string
description?: string
model: string
instructions?: string
tools?: AssistantTool[]
file_ids: string[]
metadata?: Record<string, unknown>
}
type AssistantTool = {
type: string // 'retrieval', 'code_interpreter', etc.
enabled: boolean
useTimeWeightedRetriever?: boolean
settings: any
}
Settings
type SettingComponentProps = {
key: string
title: string
description: string
controllerType: ControllerType
controllerProps: SliderComponentProps | CheckboxComponentProps | InputComponentProps | DropdownComponentProps
extensionName?: string
requireModelReload?: boolean
configType?: 'runtime' | 'setting'
}
type ControllerType = 'slider' | 'checkbox' | 'input' | 'tag' | 'dropdown'
type InputComponentProps = {
placeholder: string
value: string | string[]
type?: 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
textAlign?: 'left' | 'right'
inputActions?: ('unobscure' | 'copy')[]
}
type SliderComponentProps = {
min: number
max: number
step: number
value: number
}
type CheckboxComponentProps = {
value: boolean
}
type DropdownComponentProps = {
value: string
type?: string
options?: DropdownOption[]
recommended?: string
}
type DropdownOption = {
name: string
value: string
}
Utility Functions
File System
const fs = {
existsSync: (path: string) => Promise<boolean>
mkdir: (path: string) => Promise<void>
readdirSync: (path: string) => Promise<string[]>
readFileSync: (path: string) => Promise<string>
writeFileSync: (path: string, data: string) => Promise<void>
rm: (path: string) => Promise<void>
mv: (from: string, to: string) => Promise<void>
fileStat: (path: string) => Promise<FileStat>
}
Path Utilities
const getJanDataFolderPath: () => Promise<string>
const joinPath: (paths: string[]) => Promise<string>
const baseName: (path: string) => Promise<string>
const dirName: (path: string) => Promise<string>
const getUserHomePath: () => Promise<string>
const getResourcePath: () => Promise<string>
UI Utilities
const openFileExplorer: (path: string) => Promise<void>
const openExternalUrl: (url: string) => Promise<void>
const showToast: (title: string, message: string) => void
const log: (message: string, fileName?: string) => void
Example: Complete Extension
import {
BaseExtension,
ExtensionTypeEnum,
events,
MessageEvent,
MessageRequest,
SettingComponentProps,
fs,
joinPath,
getJanDataFolderPath
} from '@janhq/core'
export default class MyCompleteExtension extends BaseExtension {
private dataPath: string = ''
async onLoad(): Promise<void> {
// Initialize data path
const janDataPath = await getJanDataFolderPath()
this.dataPath = await joinPath([janDataPath, 'my-extension'])
// Create data directory if it doesn't exist
if (!await fs.existsSync(this.dataPath)) {
await fs.mkdir(this.dataPath)
}
// Register settings
await this.registerSettings([
{
key: 'enabled',
title: 'Enable Extension',
description: 'Turn the extension on or off',
controllerType: 'checkbox',
controllerProps: { value: true }
},
{
key: 'api_key',
title: 'API Key',
description: 'Your API key',
controllerType: 'input',
controllerProps: {
placeholder: 'Enter API key',
value: '',
type: 'password',
inputActions: ['unobscure', 'copy']
}
},
{
key: 'temperature',
title: 'Temperature',
description: 'Model temperature',
controllerType: 'slider',
controllerProps: {
min: 0,
max: 2,
step: 0.1,
value: 0.7
}
}
])
// Listen to events
const enabled = await this.getSetting('enabled', true)
if (enabled) {
events.on(MessageEvent.OnMessageSent, this.handleMessage)
}
}
onUnload(): void {
events.off(MessageEvent.OnMessageSent, this.handleMessage)
}
onSettingUpdate<T>(key: string, value: T): void {
if (key === 'enabled') {
if (value) {
events.on(MessageEvent.OnMessageSent, this.handleMessage)
} else {
events.off(MessageEvent.OnMessageSent, this.handleMessage)
}
}
}
private handleMessage = async (data: MessageRequest) => {
const apiKey = await this.getSetting('api_key', '')
const temperature = await this.getSetting('temperature', 0.7)
console.log('Processing message with temp:', temperature)
// Your message processing logic here
}
}
Next Steps
Building Extensions
Learn how to package and distribute
Examples
Explore real extension examples