Skip to main content

Extension Base Classes

Extensions are the building blocks of Jan. The BaseExtension class provides the foundation for creating custom extensions that integrate with the Jan platform.

BaseExtension Class

The BaseExtension is an abstract class that all extensions must extend. It provides core functionality for lifecycle management, settings, and model registration.

Importing

import { BaseExtension, ExtensionTypeEnum } from '@janhq/core'

Basic Structure

export 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
}

Extension Types

Extensions can specify their type using ExtensionTypeEnum.
enum ExtensionTypeEnum {
  Assistant = 'assistant',
  Conversational = 'conversational',
  Inference = 'inference',
  Model = 'model',
  SystemMonitoring = 'systemMonitoring',
  MCP = 'mcp',
  HuggingFace = 'huggingFace',
  Engine = 'engine',
  Hardware = 'hardware',
  RAG = 'rag',
  VectorDB = 'vectorDB',
}

ExtensionType Interface

interface ExtensionType {
  type(): ExtensionTypeEnum | undefined
}

Compatibility Interface

interface Compatibility {
  platform: string[]
  version: string
}

Creating an Extension

Basic Extension

import { BaseExtension, ExtensionTypeEnum } from '@janhq/core'

export default class MyExtension extends BaseExtension {
  constructor() {
    super(
      'https://example.com/extension.js',  // url
      'my-extension',                       // name
      'My Extension',                       // productName
      true,                                 // active
      'A custom extension',                 // description
      '1.0.0'                               // version
    )
  }

  type(): ExtensionTypeEnum {
    return ExtensionTypeEnum.Model
  }

  onLoad(): void {
    console.log('Extension loaded')
  }

  onUnload(): void {
    console.log('Extension unloaded')
  }
}

Extension with Compatibility

import { BaseExtension, ExtensionTypeEnum, Compatibility } from '@janhq/core'

export default class PlatformExtension extends BaseExtension {
  type(): ExtensionTypeEnum {
    return ExtensionTypeEnum.Engine
  }

  compatibility(): Compatibility {
    return {
      platform: ['darwin', 'linux', 'win32'],
      version: '>=0.5.0',
    }
  }

  onLoad(): void {
    console.log('Platform extension loaded')
  }

  onUnload(): void {
    console.log('Platform extension unloaded')
  }
}

Lifecycle Methods

onLoad()

Called when the extension is loaded. Use this for initialization logic.
onLoad(): void {
  // Initialize resources
  this.initializeDatabase()
  
  // Register event listeners
  events.on(ModelEvent.OnModelReady, this.handleModelReady)
  
  // Register models
  this.registerModels([
    /* model definitions */
  ])
}

onUnload()

Called when the extension is unloaded. Use this for cleanup logic.
onUnload(): void {
  // Remove event listeners
  events.off(ModelEvent.OnModelReady, this.handleModelReady)
  
  // Clean up resources
  this.cleanup()
}

install()

Optional method to install prerequisites for the extension.
async install(): Promise<void> {
  // Download required files
  await this.downloadDependencies()
  
  // Set up configuration
  await this.setupConfig()
}

Model Registration

registerModels()

Register models with the ModelManager.
import { BaseExtension, Model } from '@janhq/core'

export default class ModelExtension extends BaseExtension {
  async onLoad(): Promise<void> {
    const models: Model[] = [
      {
        object: 'model',
        version: '1.0',
        format: 'gguf',
        sources: [{
          filename: 'model.gguf',
          url: 'https://example.com/model.gguf',
        }],
        id: 'my-model',
        name: 'My Model',
        created: Date.now(),
        description: 'A custom model',
        settings: { ctx_len: 2048 },
        parameters: { temperature: 0.7 },
        metadata: {
          author: 'Me',
          tags: ['custom'],
          size: 4096000000,
        },
        engine: 'nitro',
      },
    ]

    await this.registerModels(models)
  }
}

Settings Management

Extensions can register and manage settings using localStorage.

registerSettings()

Register settings for the extension.
import { BaseExtension, SettingComponentProps } from '@janhq/core'

export default class ConfigurableExtension extends BaseExtension {
  async onLoad(): Promise<void> {
    const settings: SettingComponentProps[] = [
      {
        key: 'api_key',
        title: 'API Key',
        description: 'Your API key',
        controllerType: 'input',
        controllerProps: {
          placeholder: 'Enter API key',
          value: '',
          type: 'password',
        },
      },
      {
        key: 'temperature',
        title: 'Temperature',
        description: 'Model temperature',
        controllerType: 'slider',
        controllerProps: {
          min: 0,
          max: 2,
          step: 0.1,
          value: 0.7,
        },
      },
      {
        key: 'streaming',
        title: 'Enable Streaming',
        description: 'Stream responses',
        controllerType: 'checkbox',
        controllerProps: {
          value: true,
        },
      },
    ]

    await this.registerSettings(settings)
  }
}

getSetting()

Get a specific setting value.
async myMethod() {
  const apiKey = await this.getSetting<string>('api_key', '')
  const temperature = await this.getSetting<number>('temperature', 0.7)
  
  console.log('API Key:', apiKey)
  console.log('Temperature:', temperature)
}

getSettings()

Get all settings for the extension.
async myMethod() {
  const settings = await this.getSettings()
  console.log('All settings:', settings)
}

updateSettings()

Update extension settings.
async myMethod() {
  await this.updateSettings([
    {
      key: 'api_key',
      controllerProps: {
        value: 'new-api-key',
      },
    },
    {
      key: 'temperature',
      controllerProps: {
        value: 0.9,
      },
    },
  ])
}

onSettingUpdate()

Called when a setting is updated.
onSettingUpdate<T>(key: string, value: T) {
  console.log(`Setting ${key} updated to:`, value)
  
  if (key === 'api_key') {
    this.reconnectWithNewKey(value as string)
  }
}

Complete Extension Example

Here’s a complete example of a model extension with settings and event handling:
import {
  BaseExtension,
  ExtensionTypeEnum,
  Model,
  ModelInterface,
  SettingComponentProps,
  events,
  ModelEvent,
} from '@janhq/core'

export default class CustomModelExtension
  extends BaseExtension
  implements ModelInterface
{
  private apiKey: string = ''

  constructor() {
    super(
      'https://example.com/extension.js',
      'custom-model-extension',
      'Custom Model Extension',
      true,
      'Provides custom model functionality',
      '1.0.0'
    )
  }

  type(): ExtensionTypeEnum {
    return ExtensionTypeEnum.Model
  }

  compatibility() {
    return {
      platform: ['darwin', 'linux', 'win32'],
      version: '>=0.5.0',
    }
  }

  async onLoad(): Promise<void> {
    // Register settings
    await this.registerSettings([
      {
        key: 'api_key',
        title: 'API Key',
        description: 'Your API key',
        controllerType: 'input',
        controllerProps: {
          placeholder: 'Enter API key',
          value: '',
          type: 'password',
        },
      },
    ])

    // Load settings
    this.apiKey = await this.getSetting<string>('api_key', '')

    // Register event listeners
    events.on(ModelEvent.OnModelInit, this.handleModelInit)

    // Register models
    await this.registerModels(await this.fetchAvailableModels())

    console.log('Custom Model Extension loaded')
  }

  async onUnload(): Promise<void> {
    // Clean up event listeners
    events.off(ModelEvent.OnModelInit, this.handleModelInit)
    console.log('Custom Model Extension unloaded')
  }

  onSettingUpdate<T>(key: string, value: T): void {
    if (key === 'api_key') {
      this.apiKey = value as string
      console.log('API key updated')
    }
  }

  async install(): Promise<void> {
    console.log('Installing prerequisites...')
    // Download required files, setup configuration, etc.
  }

  // ModelInterface implementation
  async pullModel(model: string, id?: string, name?: string): Promise<void> {
    console.log(`Pulling model: ${model}`)
    // Implementation
  }

  async cancelModelPull(model: string): Promise<void> {
    console.log(`Cancelling pull for: ${model}`)
    // Implementation
  }

  async deleteModel(model: string): Promise<void> {
    console.log(`Deleting model: ${model}`)
    // Implementation
  }

  async getModels(): Promise<Model[]> {
    return await this.fetchAvailableModels()
  }

  async updateModel(model: Partial<Model>): Promise<Model> {
    console.log(`Updating model: ${model.id}`)
    // Implementation
    return model as Model
  }

  async importModel(
    model: string,
    modePath: string,
    name?: string
  ): Promise<void> {
    console.log(`Importing model from: ${modePath}`)
    // Implementation
  }

  async getSources(): Promise<any[]> {
    return []
  }

  async addSource(source: string): Promise<void> {
    console.log(`Adding source: ${source}`)
  }

  async deleteSource(source: string): Promise<void> {
    console.log(`Deleting source: ${source}`)
  }

  // Private methods
  private handleModelInit = (model: any) => {
    console.log('Model initializing:', model)
  }

  private async fetchAvailableModels(): Promise<Model[]> {
    // Fetch models from API
    return []
  }
}

Extension Properties

Protected Properties

protected settingFolderName = 'settings'
protected settingFileName = 'settings.json'

Public Properties

name: string              // Extension name
productName?: string      // Display name
url: string               // Extension URL
active: boolean           // Active status
description: string       // Description
version: string           // Version

Best Practices

Type Safety

Always specify the extension type for proper categorization.

Cleanup

Remove all event listeners and clean up resources in onUnload().

Error Handling

Wrap async operations in try-catch blocks.

Settings

Use settings for user-configurable options instead of hardcoding.

Async Operations

async onLoad(): Promise<void> {
  try {
    await this.initialize()
    await this.registerModels(await this.fetchModels())
  } catch (error) {
    console.error('Failed to load extension:', error)
    events.emit('Extension.LoadError', { name: this.name, error })
  }
}

Resource Cleanup

private handlers: Map<string, Function> = new Map()
private resources: any[] = []

onUnload(): void {
  // Clean up event handlers
  this.handlers.forEach((handler, event) => {
    events.off(event, handler)
  })
  this.handlers.clear()

  // Clean up resources
  this.resources.forEach(resource => resource.dispose())
  this.resources = []
}

Next Steps

Type Definitions

Explore all available types for extensions

Event System

Learn about event-driven architecture

Model Interface

Implement model management

Inference Interface

Implement inference capabilities

Build docs developers (and LLMs) love