Extensions API
The Extensions API provides comprehensive functionality for creating, managing, and interacting with Trae IDE extensions. This API enables you to build powerful extensions that enhance the development experience.
Overview
The Extensions API allows you to:
- Create and manage extension lifecycle
- Register commands, menus, and keybindings
- Contribute to the UI with custom views and panels
- Access and modify workspace settings
- Interact with other extensions
- Handle extension activation and deactivation
Core Interfaces
Extension
Represents an extension in Trae IDE.
typescript
interface Extension<T = any> {
readonly id: string;
readonly extensionUri: Uri;
readonly extensionPath: string;
readonly isActive: boolean;
readonly packageJSON: any;
readonly exports: T;
activate(): Thenable<T>;
}ExtensionContext
Provides utilities and state for an extension.
typescript
interface ExtensionContext {
readonly subscriptions: Disposable[];
readonly workspaceState: Memento;
readonly globalState: Memento & {
setKeysForSync(keys: readonly string[]): void;
};
readonly secrets: SecretStorage;
readonly extensionUri: Uri;
readonly extensionPath: string;
readonly environmentVariableCollection: EnvironmentVariableCollection;
readonly storageUri?: Uri;
readonly globalStorageUri: Uri;
readonly logUri: Uri;
readonly extensionMode: ExtensionMode;
asAbsolutePath(relativePath: string): string;
}Extension Lifecycle
Activation
Extensions are activated when specific conditions are met.
typescript
// package.json activation events
{
"activationEvents": [
"onLanguage:typescript",
"onCommand:myExtension.helloWorld",
"onDebug",
"onFileSystem:sftp",
"workspaceContains:**/.eslintrc.*",
"onStartupFinished"
]
}
// Extension entry point
export function activate(context: TraeAPI.ExtensionContext) {
console.log('Extension "my-extension" is now active!');
// Register commands
const disposable = TraeAPI.commands.registerCommand('myExtension.helloWorld', () => {
TraeAPI.window.showInformationMessage('Hello World from My Extension!');
});
context.subscriptions.push(disposable);
// Return public API
return {
sayHello() {
return 'Hello from my extension!';
}
};
}
export function deactivate() {
console.log('Extension "my-extension" is now deactivated!');
}Extension Modes
typescript
enum ExtensionMode {
Production = 1,
Development = 2,
Test = 3
}
// Check extension mode
if (context.extensionMode === TraeAPI.ExtensionMode.Development) {
console.log('Running in development mode');
}Commands
Registering Commands
typescript
// Simple command
const disposable1 = TraeAPI.commands.registerCommand('myExtension.simpleCommand', () => {
TraeAPI.window.showInformationMessage('Simple command executed!');
});
// Command with arguments
const disposable2 = TraeAPI.commands.registerCommand(
'myExtension.commandWithArgs',
(arg1: string, arg2: number) => {
TraeAPI.window.showInformationMessage(`Args: ${arg1}, ${arg2}`);
}
);
// Text editor command
const disposable3 = TraeAPI.commands.registerTextEditorCommand(
'myExtension.editorCommand',
(textEditor: TraeAPI.TextEditor, edit: TraeAPI.TextEditorEdit) => {
edit.insert(textEditor.selection.active, 'Hello from editor command!');
}
);
context.subscriptions.push(disposable1, disposable2, disposable3);Executing Commands
typescript
// Execute built-in commands
await TraeAPI.commands.executeCommand('workbench.action.files.save');
await TraeAPI.commands.executeCommand('editor.action.formatDocument');
// Execute custom commands
await TraeAPI.commands.executeCommand('myExtension.commandWithArgs', 'hello', 42);
// Get all available commands
const commands = await TraeAPI.commands.getCommands();
console.log('Available commands:', commands);UI Contributions
Status Bar
typescript
// Create status bar item
const statusBarItem = TraeAPI.window.createStatusBarItem(
TraeAPI.StatusBarAlignment.Right,
100
);
statusBarItem.text = '$(sync~spin) Processing...';
statusBarItem.tooltip = 'Click to cancel';
statusBarItem.command = 'myExtension.cancelProcessing';
statusBarItem.show();
context.subscriptions.push(statusBarItem);
// Update status bar dynamically
function updateStatus(message: string, isLoading: boolean = false) {
statusBarItem.text = isLoading ? `$(sync~spin) ${message}` : message;
statusBarItem.show();
}Tree Views
typescript
// Define tree data provider
class MyTreeDataProvider implements TraeAPI.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData = new TraeAPI.EventEmitter<TreeItem | undefined | null | void>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
getTreeItem(element: TreeItem): TraeAPI.TreeItem {
return element;
}
getChildren(element?: TreeItem): Thenable<TreeItem[]> {
if (!element) {
// Root items
return Promise.resolve([
new TreeItem('Item 1', TraeAPI.TreeItemCollapsibleState.Collapsed),
new TreeItem('Item 2', TraeAPI.TreeItemCollapsibleState.None)
]);
} else {
// Child items
return Promise.resolve([
new TreeItem('Child 1', TraeAPI.TreeItemCollapsibleState.None),
new TreeItem('Child 2', TraeAPI.TreeItemCollapsibleState.None)
]);
}
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
}
class TreeItem extends TraeAPI.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: TraeAPI.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `Tooltip for ${label}`;
this.description = 'Description';
}
}
// Register tree view
const treeDataProvider = new MyTreeDataProvider();
const treeView = TraeAPI.window.createTreeView('myExtension.treeView', {
treeDataProvider,
showCollapseAll: true
});
context.subscriptions.push(treeView);Webview Panels
typescript
// Create webview panel
function createWebviewPanel() {
const panel = TraeAPI.window.createWebviewPanel(
'myExtension.webview',
'My Extension Panel',
TraeAPI.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [TraeAPI.Uri.joinPath(context.extensionUri, 'media')]
}
);
// Set HTML content
panel.webview.html = getWebviewContent(panel.webview, context.extensionUri);
// Handle messages from webview
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'alert':
TraeAPI.window.showErrorMessage(message.text);
return;
case 'getData':
panel.webview.postMessage({
command: 'dataResponse',
data: { items: ['item1', 'item2', 'item3'] }
});
return;
}
},
undefined,
context.subscriptions
);
return panel;
}
function getWebviewContent(webview: TraeAPI.Webview, extensionUri: TraeAPI.Uri): string {
const scriptUri = webview.asWebviewUri(
TraeAPI.Uri.joinPath(extensionUri, 'media', 'main.js')
);
const styleUri = webview.asWebviewUri(
TraeAPI.Uri.joinPath(extensionUri, 'media', 'main.css')
);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet">
<title>My Extension</title>
</head>
<body>
<h1>Hello from Webview!</h1>
<button id="alertBtn">Show Alert</button>
<button id="getDataBtn">Get Data</button>
<div id="dataContainer"></div>
<script src="${scriptUri}"></script>
</body>
</html>`;
}Configuration
Contributing Settings
typescript
// package.json configuration contribution
{
"contributes": {
"configuration": {
"title": "My Extension",
"properties": {
"myExtension.enable": {
"type": "boolean",
"default": true,
"description": "Enable My Extension"
},
"myExtension.maxItems": {
"type": "number",
"default": 10,
"minimum": 1,
"maximum": 100,
"description": "Maximum number of items"
},
"myExtension.customPath": {
"type": "string",
"default": "",
"description": "Custom path for extension"
}
}
}
}
}Reading Configuration
typescript
// Get configuration
const config = TraeAPI.workspace.getConfiguration('myExtension');
const isEnabled = config.get<boolean>('enable', true);
const maxItems = config.get<number>('maxItems', 10);
const customPath = config.get<string>('customPath', '');
// Listen to configuration changes
TraeAPI.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('myExtension')) {
console.log('My extension configuration changed');
if (event.affectsConfiguration('myExtension.enable')) {
const newValue = TraeAPI.workspace.getConfiguration('myExtension').get('enable');
console.log('Enable setting changed to:', newValue);
}
}
});Updating Configuration
typescript
// Update configuration
const config = TraeAPI.workspace.getConfiguration('myExtension');
// Update global setting
await config.update('maxItems', 20, TraeAPI.ConfigurationTarget.Global);
// Update workspace setting
await config.update('enable', false, TraeAPI.ConfigurationTarget.Workspace);
// Update workspace folder setting
const workspaceFolder = TraeAPI.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
await config.update('customPath', '/custom/path', TraeAPI.ConfigurationTarget.WorkspaceFolder);
}State Management
Workspace State
typescript
// Store workspace-specific data
const workspaceState = context.workspaceState;
// Store data
await workspaceState.update('lastOpenedFile', '/path/to/file.ts');
await workspaceState.update('userPreferences', { theme: 'dark', fontSize: 14 });
// Retrieve data
const lastFile = workspaceState.get<string>('lastOpenedFile');
const preferences = workspaceState.get<any>('userPreferences', { theme: 'light', fontSize: 12 });
// Get all keys
const keys = workspaceState.keys();
console.log('Stored keys:', keys);Global State
typescript
// Store global data (across all workspaces)
const globalState = context.globalState;
// Store data
await globalState.update('extensionVersion', '1.0.0');
await globalState.update('globalSettings', { autoUpdate: true });
// Retrieve data
const version = globalState.get<string>('extensionVersion');
const settings = globalState.get<any>('globalSettings', {});
// Sync data across devices
globalState.setKeysForSync(['globalSettings', 'userProfile']);Secret Storage
typescript
// Store sensitive data
const secrets = context.secrets;
// Store secret
await secrets.store('apiKey', 'your-secret-api-key');
await secrets.store('token', 'user-auth-token');
// Retrieve secret
const apiKey = await secrets.get('apiKey');
const token = await secrets.get('token');
// Delete secret
await secrets.delete('oldToken');
// Listen to secret changes
secrets.onDidChange(event => {
console.log('Secret changed:', event.key);
});Inter-Extension Communication
Extension API
typescript
// Extension A - Export API
export function activate(context: TraeAPI.ExtensionContext) {
// Return public API
return {
getVersion(): string {
return '1.0.0';
},
processData(data: any): Promise<any> {
return new Promise(resolve => {
// Process data
resolve({ processed: true, data });
});
},
onDataChanged: new TraeAPI.EventEmitter<any>().event
};
}
// Extension B - Use Extension A's API
export function activate(context: TraeAPI.ExtensionContext) {
const extensionA = TraeAPI.extensions.getExtension('publisher.extensionA');
if (extensionA) {
extensionA.activate().then(api => {
console.log('Extension A version:', api.getVersion());
// Use API
api.processData({ test: 'data' }).then(result => {
console.log('Processed result:', result);
});
// Listen to events
api.onDataChanged(data => {
console.log('Data changed:', data);
});
});
}
}Extension Dependencies
typescript
// package.json - Declare dependencies
{
"extensionDependencies": [
"publisher.requiredExtension"
],
"extensionPack": [
"publisher.relatedExtension1",
"publisher.relatedExtension2"
]
}
// Check if extension is installed and active
function checkExtensionDependency(extensionId: string): boolean {
const extension = TraeAPI.extensions.getExtension(extensionId);
return extension !== undefined && extension.isActive;
}
// Wait for extension to activate
async function waitForExtension(extensionId: string): Promise<any> {
const extension = TraeAPI.extensions.getExtension(extensionId);
if (!extension) {
throw new Error(`Extension ${extensionId} not found`);
}
if (!extension.isActive) {
await extension.activate();
}
return extension.exports;
}File System Access
Reading Files
typescript
// Read file content
const fileUri = TraeAPI.Uri.file('/path/to/file.txt');
const fileContent = await TraeAPI.workspace.fs.readFile(fileUri);
const textContent = Buffer.from(fileContent).toString('utf8');
// Read directory
const dirUri = TraeAPI.Uri.file('/path/to/directory');
const entries = await TraeAPI.workspace.fs.readDirectory(dirUri);
entries.forEach(([name, type]) => {
console.log(`${name} (${type === TraeAPI.FileType.Directory ? 'dir' : 'file'})`);
});
// Check if file exists
try {
const stat = await TraeAPI.workspace.fs.stat(fileUri);
console.log('File exists, size:', stat.size);
} catch (error) {
console.log('File does not exist');
}Writing Files
typescript
// Write file
const content = Buffer.from('Hello, World!', 'utf8');
await TraeAPI.workspace.fs.writeFile(fileUri, content);
// Create directory
const newDirUri = TraeAPI.Uri.file('/path/to/new/directory');
await TraeAPI.workspace.fs.createDirectory(newDirUri);
// Copy file
const sourceUri = TraeAPI.Uri.file('/path/to/source.txt');
const targetUri = TraeAPI.Uri.file('/path/to/target.txt');
await TraeAPI.workspace.fs.copy(sourceUri, targetUri);
// Delete file
await TraeAPI.workspace.fs.delete(fileUri);File Watchers
typescript
// Watch for file changes
const watcher = TraeAPI.workspace.createFileSystemWatcher('**/*.ts');
watcher.onDidCreate(uri => {
console.log('File created:', uri.fsPath);
});
watcher.onDidChange(uri => {
console.log('File changed:', uri.fsPath);
});
watcher.onDidDelete(uri => {
console.log('File deleted:', uri.fsPath);
});
context.subscriptions.push(watcher);Advanced Features
Custom Language Support
typescript
// Register language
const disposable = TraeAPI.languages.setLanguageConfiguration('mylang', {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
]
});
context.subscriptions.push(disposable);Custom Themes
typescript
// package.json theme contribution
{
"contributes": {
"themes": [
{
"label": "My Dark Theme",
"uiTheme": "vs-dark",
"path": "./themes/dark-theme.json"
}
]
}
}
// Theme file structure (dark-theme.json)
{
"name": "My Dark Theme",
"type": "dark",
"colors": {
"editor.background": "#1e1e1e",
"editor.foreground": "#d4d4d4",
"activityBar.background": "#2d2d30",
"sideBar.background": "#252526"
},
"tokenColors": [
{
"scope": "comment",
"settings": {
"foreground": "#6A9955",
"fontStyle": "italic"
}
}
]
}Custom Icons
typescript
// package.json icon theme contribution
{
"contributes": {
"iconThemes": [
{
"id": "myIconTheme",
"label": "My Icon Theme",
"path": "./icons/icon-theme.json"
}
]
}
}
// Icon theme file
{
"iconDefinitions": {
"file": {
"iconPath": "./icons/file.svg"
},
"folder": {
"iconPath": "./icons/folder.svg"
}
},
"file": "file",
"folder": "folder",
"fileExtensions": {
"js": "javascript",
"ts": "typescript"
}
}Testing Extensions
Unit Tests
typescript
// test/suite/extension.test.ts
import * as assert from 'assert';
import * as TraeAPI from '@trae/api';
import * as myExtension from '../../src/extension';
suite('Extension Test Suite', () => {
TraeAPI.window.showInformationMessage('Start all tests.');
test('Extension should be present', () => {
assert.ok(TraeAPI.extensions.getExtension('publisher.my-extension'));
});
test('Should register commands', async () => {
const commands = await TraeAPI.commands.getCommands();
assert.ok(commands.includes('myExtension.helloWorld'));
});
test('Command should execute', async () => {
await TraeAPI.commands.executeCommand('myExtension.helloWorld');
// Assert expected behavior
});
});Integration Tests
typescript
// test/suite/integration.test.ts
import * as assert from 'assert';
import * as TraeAPI from '@trae/api';
import * as path from 'path';
suite('Integration Test Suite', () => {
let document: TraeAPI.TextDocument;
let editor: TraeAPI.TextEditor;
suiteSetup(async () => {
const uri = TraeAPI.Uri.file(path.join(__dirname, '../../test-fixtures/test.ts'));
document = await TraeAPI.workspace.openTextDocument(uri);
editor = await TraeAPI.window.showTextDocument(document);
});
test('Should format document', async () => {
await TraeAPI.commands.executeCommand('editor.action.formatDocument');
// Assert formatting changes
});
test('Should provide completions', async () => {
const position = new TraeAPI.Position(0, 0);
const completions = await TraeAPI.commands.executeCommand(
'vscode.executeCompletionItemProvider',
document.uri,
position
);
assert.ok(completions);
});
});Publishing Extensions
Package Configuration
json
{
"name": "my-extension",
"displayName": "My Extension",
"description": "A sample extension",
"version": "1.0.0",
"publisher": "my-publisher",
"engines": {
"trae": "^1.0.0"
},
"categories": [
"Other"
],
"keywords": [
"sample",
"extension"
],
"main": "./out/extension.js",
"scripts": {
"compile": "tsc -p ./",
"package": "trae-cli package",
"publish": "trae-cli publish"
}
}Build and Package
bash
# Install dependencies
npm install
# Compile TypeScript
npm run compile
# Package extension
npm run package
# Publish to marketplace
npm run publishBest Practices
Performance
- Lazy load heavy dependencies
- Use activation events appropriately
- Dispose of resources properly
- Avoid blocking the main thread
User Experience
- Provide clear error messages
- Use progress indicators for long operations
- Follow UI guidelines and conventions
- Test on different platforms
Security
- Validate user inputs
- Use secure storage for sensitive data
- Follow principle of least privilege
- Sanitize data before display
Examples
Complete Extension Example
typescript
// src/extension.ts
import * as TraeAPI from '@trae/api';
export function activate(context: TraeAPI.ExtensionContext) {
console.log('My extension is now active!');
// Register commands
const commands = [
TraeAPI.commands.registerCommand('myExtension.helloWorld', () => {
TraeAPI.window.showInformationMessage('Hello World!');
}),
TraeAPI.commands.registerCommand('myExtension.showInfo', () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
const document = editor.document;
const selection = editor.selection;
const text = document.getText(selection);
TraeAPI.window.showInformationMessage(
`Selected text: "${text}" in ${document.fileName}`
);
}
})
];
// Create status bar item
const statusBarItem = TraeAPI.window.createStatusBarItem(
TraeAPI.StatusBarAlignment.Right,
100
);
statusBarItem.text = '$(heart) My Extension';
statusBarItem.command = 'myExtension.helloWorld';
statusBarItem.show();
// Add to subscriptions
context.subscriptions.push(...commands, statusBarItem);
// Return API
return {
getExtensionInfo() {
return {
name: 'My Extension',
version: '1.0.0'
};
}
};
}
export function deactivate() {
console.log('My extension is now deactivated!');
}For more examples and detailed guides, see the Extension Development Guide and Extension Samples repository.