Documentation Index
Fetch the complete documentation index at: https://mintlify.com/janhq/jan/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks you through creating a basic Jan extension that listens to message events and provides custom responses.
Prerequisites
- Node.js 18.0.0 or higher
- Yarn package manager
- Basic TypeScript knowledge
- Jan application installed
Step 1: Set Up Your Project
Clone the Extension Template
git clone https://github.com/janhq/extension-template my-extension
cd my-extension
Install Dependencies
Step 2: Update package.json
Edit package.json with your extension details:
{
"name": "@myorg/my-extension",
"productName": "My Extension",
"version": "1.0.0",
"description": "My first Jan extension",
"main": "dist/index.js",
"author": "Your Name <you@example.com>",
"license": "MIT",
"scripts": {
"build": "rolldown -c rolldown.config.mjs",
"build:publish": "yarn build && npm pack"
},
"dependencies": {
"@janhq/core": "latest"
},
"engines": {
"node": ">=18.0.0"
}
}
Step 3: Create Your Extension
Edit src/index.ts:
import {
BaseExtension,
ExtensionTypeEnum,
events,
MessageEvent,
MessageRequest,
ThreadMessage,
ChatCompletionRole,
ContentType,
MessageStatus
} from '@janhq/core'
export default class MyExtension extends BaseExtension {
constructor() {
super(
'', // URL will be set by Jan
'my-extension', // Extension name
'My Extension', // Product name
true, // Active by default
'My first Jan extension', // Description
'1.0.0' // Version
)
}
async onLoad(): Promise<void> {
console.log('My extension loaded!')
// Listen for message sent events
events.on(MessageEvent.OnMessageSent, this.handleMessageSent)
}
onUnload(): void {
console.log('My extension unloaded')
// Clean up event listeners
events.off(MessageEvent.OnMessageSent, this.handleMessageSent)
}
private handleMessageSent = async (data: MessageRequest) => {
console.log('Message sent:', data)
// Example: Log when user mentions a specific keyword
const lastMessage = data.messages?.[data.messages.length - 1]
if (lastMessage?.content?.toString().includes('hello')) {
console.log('User said hello!')
}
}
}
Step 4: Build Your Extension
Compile your extension:
This generates a dist/ folder with your compiled extension.
Step 5: Package Your Extension
Create a .tgz package:
This creates a file like myorg-my-extension-1.0.0.tgz.
Step 6: Install in Jan
- Open Jan application
- Go to Settings > Extensions
- Click Manual Installation
- Select your
.tgz file
- Restart Jan if prompted
Step 7: Verify Installation
Check the console logs (Help > Toggle Developer Tools) to see:
Send a message containing “hello” and you should see:
Example: Advanced Extension with Settings
Let’s add settings to customize behavior:
import {
BaseExtension,
events,
MessageEvent,
MessageRequest,
SettingComponentProps
} from '@janhq/core'
export default class MyExtension extends BaseExtension {
private triggerWord: string = 'hello'
async onLoad(): Promise<void> {
// Register settings
const settings: SettingComponentProps[] = [
{
key: 'trigger_word',
title: 'Trigger Word',
description: 'Word to trigger the extension',
controllerType: 'input',
controllerProps: {
placeholder: 'Enter trigger word',
value: 'hello',
type: 'text'
}
},
{
key: 'enabled',
title: 'Enable Extension',
description: 'Turn extension on or off',
controllerType: 'checkbox',
controllerProps: {
value: true
}
}
]
await this.registerSettings(settings)
// Load saved settings
this.triggerWord = await this.getSetting('trigger_word', 'hello')
const enabled = await this.getSetting('enabled', true)
if (enabled) {
events.on(MessageEvent.OnMessageSent, this.handleMessageSent)
}
}
onUnload(): void {
events.off(MessageEvent.OnMessageSent, this.handleMessageSent)
}
// Handle setting updates
onSettingUpdate<T>(key: string, value: T): void {
if (key === 'trigger_word') {
this.triggerWord = value as string
console.log('Trigger word updated to:', this.triggerWord)
} else if (key === 'enabled') {
if (value) {
events.on(MessageEvent.OnMessageSent, this.handleMessageSent)
} else {
events.off(MessageEvent.OnMessageSent, this.handleMessageSent)
}
}
}
private handleMessageSent = async (data: MessageRequest) => {
const lastMessage = data.messages?.[data.messages.length - 1]
if (lastMessage?.content?.toString().includes(this.triggerWord)) {
console.log(`Trigger word "${this.triggerWord}" detected!`)
}
}
}
Example Extension Types
Inference Extension
import { InferenceExtension, MessageRequest, ThreadMessage } from '@janhq/core'
export default class MyInferenceExtension extends InferenceExtension {
async inference(data: MessageRequest): Promise<ThreadMessage> {
// Implement your inference logic
return {
id: 'msg_' + Date.now(),
object: 'thread.message',
thread_id: data.threadId,
role: ChatCompletionRole.Assistant,
content: [{
type: ContentType.Text,
text: {
value: 'Custom response',
annotations: []
}
}],
status: MessageStatus.Ready,
created_at: Date.now(),
completed_at: Date.now()
}
}
}
Conversational Extension
import { ConversationalExtension, Thread, ThreadMessage } from '@janhq/core'
export default class MyConversationalExtension extends ConversationalExtension {
async listThreads(): Promise<Thread[]> {
// Return list of threads
return []
}
async createThread(thread: Partial<Thread>): Promise<Thread> {
// Create and return new thread
return thread as Thread
}
async modifyThread(thread: Thread): Promise<void> {
// Update thread
}
async deleteThread(threadId: string): Promise<void> {
// Delete thread
}
async createMessage(message: Partial<ThreadMessage>): Promise<ThreadMessage> {
// Create and return new message
return message as ThreadMessage
}
async listMessages(threadId: string): Promise<ThreadMessage[]> {
// Return messages for thread
return []
}
// ... implement other methods
}
Debugging Tips
- Check the console for errors (Help > Toggle Developer Tools)
- Verify
package.json has correct main field pointing to dist/index.js
- Ensure extension is built:
yarn build
- Try reinstalling the extension
- Verify
registerSettings() is called in onLoad()
- Check setting configuration matches
SettingComponentProps interface
- Restart Jan after installing extension
- Ensure you’re using the correct event name from
@janhq/core
- Check that
events.on() is called after Jan finishes loading
- Verify event handler function is bound correctly (use arrow functions)
Next Steps
Core API Reference
Explore all available APIs
Building Extensions
Learn packaging and distribution