Skip to content

Extension Development Guide

This comprehensive guide covers everything you need to know about developing extensions for Trae, from basic concepts to advanced techniques.

Getting Started

Prerequisites

  • Node.js 16.x or higher
  • npm or yarn package manager
  • TypeScript knowledge (recommended)
  • Basic understanding of Trae's architecture

Development Environment Setup

bash
# Install Trae Extension CLI
npm install -g @trae/extension-cli

# Create a new extension project
trae-ext create my-extension
cd my-extension

# Install dependencies
npm install

# Start development mode
npm run dev

Project Structure

my-extension/
├── src/
│   ├── extension.ts          # Main extension entry point
│   ├── commands/             # Command implementations
│   ├── providers/            # Language service providers
│   ├── views/               # Custom views and panels
│   └── utils/               # Utility functions
├── resources/
│   ├── icons/               # Extension icons
│   └── themes/              # Custom themes
├── package.json             # Extension manifest
├── tsconfig.json           # TypeScript configuration
└── webpack.config.js       # Build configuration

Extension Manifest

package.json Configuration

json
{
  "name": "my-extension",
  "displayName": "My Awesome Extension",
  "description": "A sample extension for Trae",
  "version": "1.0.0",
  "publisher": "your-publisher-name",
  "engines": {
    "trae": "^3.0.0"
  },
  "categories": [
    "Programming Languages",
    "Themes",
    "Debuggers"
  ],
  "keywords": [
    "typescript",
    "javascript",
    "productivity"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "myExtension.helloWorld",
        "title": "Hello World",
        "category": "My Extension"
      }
    ],
    "keybindings": [
      {
        "command": "myExtension.helloWorld",
        "key": "ctrl+shift+h",
        "mac": "cmd+shift+h",
        "when": "editorTextFocus"
      }
    ],
    "menus": {
      "commandPalette": [
        {
          "command": "myExtension.helloWorld",
          "when": "true"
        }
      ]
    },
    "configuration": {
      "title": "My Extension",
      "properties": {
        "myExtension.enable": {
          "type": "boolean",
          "default": true,
          "description": "Enable My Extension"
        }
      }
    }
  },
  "activationEvents": [
    "onCommand:myExtension.helloWorld",
    "onLanguage:typescript"
  ],
  "scripts": {
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "package": "trae-ext package",
    "publish": "trae-ext publish"
  },
  "devDependencies": {
    "@trae/extension-api": "^3.0.0",
    "@types/node": "^16.x",
    "typescript": "^4.x"
  }
}

Basic Extension Structure

Main Extension File

typescript
// src/extension.ts
import * as trae from '@trae/extension-api';

// Extension activation function
export function activate(context: trae.ExtensionContext) {
    console.log('My Extension is now active!');

    // Register commands
    const disposable = trae.commands.registerCommand(
        'myExtension.helloWorld',
        () => {
            trae.window.showInformationMessage('Hello World from My Extension!');
        }
    );

    context.subscriptions.push(disposable);

    // Register other features
    registerLanguageFeatures(context);
    registerCustomViews(context);
    registerEventHandlers(context);
}

// Extension deactivation function
export function deactivate() {
    console.log('My Extension is now deactivated!');
}

function registerLanguageFeatures(context: trae.ExtensionContext) {
    // Register language features like completion, hover, etc.
    const provider = new MyCompletionProvider();
    const disposable = trae.languages.registerCompletionItemProvider(
        { scheme: 'file', language: 'typescript' },
        provider,
        '.', '"', "'"
    );
    context.subscriptions.push(disposable);
}

function registerCustomViews(context: trae.ExtensionContext) {
    // Register custom views and panels
    const provider = new MyTreeDataProvider();
    trae.window.createTreeView('myExtension.explorer', {
        treeDataProvider: provider,
        showCollapseAll: true
    });
}

function registerEventHandlers(context: trae.ExtensionContext) {
    // Register event handlers
    const disposable = trae.workspace.onDidChangeTextDocument((event) => {
        console.log('Document changed:', event.document.fileName);
    });
    context.subscriptions.push(disposable);
}

Core Extension APIs

Commands API

typescript
// Register a command
const disposable = trae.commands.registerCommand(
    'myExtension.myCommand',
    (arg1: string, arg2: number) => {
        // Command implementation
        trae.window.showInformationMessage(`Args: ${arg1}, ${arg2}`);
    }
);

// Execute a command
trae.commands.executeCommand('workbench.action.files.save');

// Get all available commands
const commands = await trae.commands.getCommands();

Window API

typescript
// Show messages
trae.window.showInformationMessage('Info message');
trae.window.showWarningMessage('Warning message');
trae.window.showErrorMessage('Error message');

// Show input box
const input = await trae.window.showInputBox({
    prompt: 'Enter your name',
    placeHolder: 'Name',
    validateInput: (value) => {
        return value.length < 3 ? 'Name must be at least 3 characters' : null;
    }
});

// Show quick pick
const selection = await trae.window.showQuickPick(
    ['Option 1', 'Option 2', 'Option 3'],
    {
        placeHolder: 'Select an option',
        canPickMany: false
    }
);

// Show progress
trae.window.withProgress({
    location: trae.ProgressLocation.Notification,
    title: 'Processing...',
    cancellable: true
}, async (progress, token) => {
    for (let i = 0; i < 100; i++) {
        if (token.isCancellationRequested) {
            break;
        }
        progress.report({ increment: 1, message: `Step ${i + 1}` });
        await new Promise(resolve => setTimeout(resolve, 100));
    }
});

Workspace API

typescript
// Get workspace folders
const workspaceFolders = trae.workspace.workspaceFolders;

// Read file
const fileUri = trae.Uri.file('/path/to/file.txt');
const content = await trae.workspace.fs.readFile(fileUri);

// Write file
const data = Buffer.from('Hello, World!', 'utf8');
await trae.workspace.fs.writeFile(fileUri, data);

// Watch files
const watcher = trae.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);
});

// Get configuration
const config = trae.workspace.getConfiguration('myExtension');
const enableFeature = config.get<boolean>('enable', true);

// Update configuration
await config.update('enable', false, trae.ConfigurationTarget.Global);

Languages API

typescript
// Completion provider
class MyCompletionProvider implements trae.CompletionItemProvider {
    provideCompletionItems(
        document: trae.TextDocument,
        position: trae.Position,
        token: trae.CancellationToken,
        context: trae.CompletionContext
    ): trae.ProviderResult<trae.CompletionItem[]> {
        const completionItems: trae.CompletionItem[] = [];
        
        // Add completion items
        const item = new trae.CompletionItem('myFunction', trae.CompletionItemKind.Function);
        item.detail = 'My custom function';
        item.documentation = 'This is a custom function provided by my extension';
        item.insertText = new trae.SnippetString('myFunction(${1:param})$0');
        
        completionItems.push(item);
        return completionItems;
    }
}

// Hover provider
class MyHoverProvider implements trae.HoverProvider {
    provideHover(
        document: trae.TextDocument,
        position: trae.Position,
        token: trae.CancellationToken
    ): trae.ProviderResult<trae.Hover> {
        const range = document.getWordRangeAtPosition(position);
        const word = document.getText(range);
        
        if (word === 'myKeyword') {
            return new trae.Hover(
                new trae.MarkdownString('**My Keyword**: This is a special keyword'),
                range
            );
        }
    }
}

// Diagnostic provider
class MyDiagnosticProvider {
    private diagnosticCollection: trae.DiagnosticCollection;
    
    constructor() {
        this.diagnosticCollection = trae.languages.createDiagnosticCollection('myExtension');
    }
    
    updateDiagnostics(document: trae.TextDocument) {
        const diagnostics: trae.Diagnostic[] = [];
        
        // Analyze document and create diagnostics
        const text = document.getText();
        const regex = /TODO:/g;
        let match;
        
        while ((match = regex.exec(text)) !== null) {
            const startPos = document.positionAt(match.index);
            const endPos = document.positionAt(match.index + match[0].length);
            const range = new trae.Range(startPos, endPos);
            
            const diagnostic = new trae.Diagnostic(
                range,
                'TODO comment found',
                trae.DiagnosticSeverity.Information
            );
            diagnostic.source = 'My Extension';
            
            diagnostics.push(diagnostic);
        }
        
        this.diagnosticCollection.set(document.uri, diagnostics);
    }
}

Advanced Features

Custom Views and Panels

typescript
// Tree view provider
class MyTreeDataProvider implements trae.TreeDataProvider<MyTreeItem> {
    private _onDidChangeTreeData: trae.EventEmitter<MyTreeItem | undefined | null | void> = new trae.EventEmitter<MyTreeItem | undefined | null | void>();
    readonly onDidChangeTreeData: trae.Event<MyTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;

    getTreeItem(element: MyTreeItem): trae.TreeItem {
        return element;
    }

    getChildren(element?: MyTreeItem): Thenable<MyTreeItem[]> {
        if (!element) {
            // Return root items
            return Promise.resolve([
                new MyTreeItem('Item 1', trae.TreeItemCollapsibleState.Collapsed),
                new MyTreeItem('Item 2', trae.TreeItemCollapsibleState.None)
            ]);
        } else {
            // Return children of element
            return Promise.resolve([
                new MyTreeItem('Child 1', trae.TreeItemCollapsibleState.None),
                new MyTreeItem('Child 2', trae.TreeItemCollapsibleState.None)
            ]);
        }
    }

    refresh(): void {
        this._onDidChangeTreeData.fire();
    }
}

class MyTreeItem extends trae.TreeItem {
    constructor(
        public readonly label: string,
        public readonly collapsibleState: trae.TreeItemCollapsibleState
    ) {
        super(label, collapsibleState);
        this.tooltip = `Tooltip for ${this.label}`;
        this.description = 'Description';
    }

    iconPath = {
        light: path.join(__filename, '..', '..', 'resources', 'light', 'item.svg'),
        dark: path.join(__filename, '..', '..', 'resources', 'dark', 'item.svg')
    };

    contextValue = 'myTreeItem';
}

// Webview panel
function createWebviewPanel(context: trae.ExtensionContext) {
    const panel = trae.window.createWebviewPanel(
        'myExtension.webview',
        'My Extension Panel',
        trae.ViewColumn.One,
        {
            enableScripts: true,
            retainContextWhenHidden: true
        }
    );

    panel.webview.html = getWebviewContent();

    // Handle messages from webview
    panel.webview.onDidReceiveMessage(
        message => {
            switch (message.command) {
                case 'alert':
                    trae.window.showErrorMessage(message.text);
                    return;
            }
        },
        undefined,
        context.subscriptions
    );
}

function getWebviewContent(): string {
    return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>My Extension Panel</title>
        <style>
            body {
                font-family: var(--trae-font-family);
                color: var(--trae-foreground);
                background-color: var(--trae-editor-background);
                padding: 20px;
            }
            button {
                background-color: var(--trae-button-background);
                color: var(--trae-button-foreground);
                border: none;
                padding: 8px 16px;
                cursor: pointer;
                border-radius: 4px;
            }
        </style>
    </head>
    <body>
        <h1>My Extension Panel</h1>
        <p>This is a custom webview panel.</p>
        <button onclick="sendMessage()">Send Message</button>
        
        <script>
            const vscode = acquireVsCodeApi();
            
            function sendMessage() {
                vscode.postMessage({
                    command: 'alert',
                    text: 'Hello from webview!'
                });
            }
        </script>
    </body>
    </html>
    `;
}

Configuration and Settings

typescript
// Define configuration schema in package.json
{
  "contributes": {
    "configuration": {
      "title": "My Extension",
      "properties": {
        "myExtension.feature.enabled": {
          "type": "boolean",
          "default": true,
          "description": "Enable the main feature"
        },
        "myExtension.feature.timeout": {
          "type": "number",
          "default": 5000,
          "minimum": 1000,
          "maximum": 30000,
          "description": "Timeout in milliseconds"
        },
        "myExtension.feature.mode": {
          "type": "string",
          "enum": ["auto", "manual", "disabled"],
          "default": "auto",
          "description": "Operation mode"
        }
      }
    }
  }
}

// Access configuration in code
class ConfigurationManager {
    private config: trae.WorkspaceConfiguration;
    
    constructor() {
        this.config = trae.workspace.getConfiguration('myExtension');
        
        // Listen for configuration changes
        trae.workspace.onDidChangeConfiguration((event) => {
            if (event.affectsConfiguration('myExtension')) {
                this.updateConfiguration();
            }
        });
    }
    
    get isEnabled(): boolean {
        return this.config.get<boolean>('feature.enabled', true);
    }
    
    get timeout(): number {
        return this.config.get<number>('feature.timeout', 5000);
    }
    
    get mode(): string {
        return this.config.get<string>('feature.mode', 'auto');
    }
    
    async setEnabled(enabled: boolean): Promise<void> {
        await this.config.update('feature.enabled', enabled, trae.ConfigurationTarget.Global);
    }
    
    private updateConfiguration(): void {
        this.config = trae.workspace.getConfiguration('myExtension');
        // Notify other components about configuration changes
    }
}

Testing Extensions

typescript
// test/extension.test.ts
import * as assert from 'assert';
import * as trae from '@trae/extension-api';
import * as myExtension from '../src/extension';

suite('Extension Test Suite', () => {
    trae.window.showInformationMessage('Start all tests.');

    test('Extension should be present', () => {
        assert.ok(trae.extensions.getExtension('publisher.my-extension'));
    });

    test('Should register commands', async () => {
        const commands = await trae.commands.getCommands();
        assert.ok(commands.includes('myExtension.helloWorld'));
    });

    test('Command should execute', async () => {
        await trae.commands.executeCommand('myExtension.helloWorld');
        // Add assertions based on command behavior
    });

    test('Configuration should work', () => {
        const config = trae.workspace.getConfiguration('myExtension');
        assert.strictEqual(typeof config.get('feature.enabled'), 'boolean');
    });
});

Launch Configuration for Testing

json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}"
            ],
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: compile"
        },
        {
            "name": "Extension Tests",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}",
                "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
            ],
            "outFiles": [
                "${workspaceFolder}/out/test/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: compile"
        }
    ]
}

Publishing Extensions

Packaging

bash
# Install packaging tool
npm install -g @trae/extension-cli

# Package extension
trae-ext package

# This creates a .trex file (Trae Extension)

Publishing to Marketplace

bash
# Login to marketplace
trae-ext login

# Publish extension
trae-ext publish

# Publish specific version
trae-ext publish --version 1.0.1

# Publish pre-release
trae-ext publish --pre-release

Marketplace Configuration

json
// package.json marketplace fields
{
  "publisher": "your-publisher-name",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/my-extension.git"
  },
  "bugs": {
    "url": "https://github.com/username/my-extension/issues"
  },
  "homepage": "https://github.com/username/my-extension#readme",
  "license": "MIT",
  "icon": "images/icon.png",
  "galleryBanner": {
    "color": "#C80000",
    "theme": "dark"
  },
  "badges": [
    {
      "url": "https://img.shields.io/badge/build-passing-brightgreen",
      "href": "https://github.com/username/my-extension/actions",
      "description": "Build Status"
    }
  ]
}

Best Practices

Performance

  1. Lazy Loading: Only activate when needed
  2. Efficient Event Handling: Debounce frequent events
  3. Memory Management: Dispose of resources properly
  4. Async Operations: Use async/await for I/O operations
typescript
// Lazy loading example
let heavyFeature: HeavyFeature | undefined;

function getHeavyFeature(): HeavyFeature {
    if (!heavyFeature) {
        heavyFeature = new HeavyFeature();
    }
    return heavyFeature;
}

// Debouncing example
let timeout: NodeJS.Timeout | undefined;

trae.workspace.onDidChangeTextDocument((event) => {
    if (timeout) {
        clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
        // Process document change
        processDocumentChange(event.document);
    }, 500);
});

// Proper disposal
export function deactivate() {
    if (heavyFeature) {
        heavyFeature.dispose();
    }
}

Error Handling

typescript
// Graceful error handling
async function safeOperation() {
    try {
        const result = await riskyOperation();
        return result;
    } catch (error) {
        console.error('Operation failed:', error);
        trae.window.showErrorMessage(`Operation failed: ${error.message}`);
        return null;
    }
}

// User-friendly error messages
function handleError(error: Error, context: string) {
    const message = `${context}: ${error.message}`;
    console.error(message, error);
    
    // Show appropriate message based on error type
    if (error.name === 'NetworkError') {
        trae.window.showErrorMessage('Network connection failed. Please check your internet connection.');
    } else {
        trae.window.showErrorMessage(message);
    }
}

Security

typescript
// Validate user input
function validateInput(input: string): boolean {
    // Sanitize and validate input
    const sanitized = input.trim();
    if (sanitized.length === 0) {
        return false;
    }
    
    // Check for malicious patterns
    const dangerousPatterns = [/<script/i, /javascript:/i, /on\w+=/i];
    return !dangerousPatterns.some(pattern => pattern.test(sanitized));
}

// Secure webview content
function getSecureWebviewContent(): string {
    return `
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${getNonce()}'">
    </head>
    <body>
        <!-- Content -->
    </body>
    </html>
    `;
}

function getNonce(): string {
    let text = '';
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < 32; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
}

Debugging Extensions

Debug Configuration

json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}"
            ],
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ],
            "preLaunchTask": "npm: compile",
            "sourceMaps": true,
            "smartStep": true,
            "skipFiles": [
                "<node_internals>/**"
            ]
        }
    ]
}

Logging

typescript
// Create output channel for logging
const outputChannel = trae.window.createOutputChannel('My Extension');

class Logger {
    static info(message: string): void {
        const timestamp = new Date().toISOString();
        outputChannel.appendLine(`[INFO ${timestamp}] ${message}`);
    }
    
    static error(message: string, error?: Error): void {
        const timestamp = new Date().toISOString();
        outputChannel.appendLine(`[ERROR ${timestamp}] ${message}`);
        if (error) {
            outputChannel.appendLine(`Stack trace: ${error.stack}`);
        }
    }
    
    static show(): void {
        outputChannel.show();
    }
}

Migration Guide

From Extension API v2 to v3

typescript
// v2 (deprecated)
trae.workspace.rootPath; // Deprecated
trae.window.showInputBox(options, token); // Changed signature

// v3 (current)
trae.workspace.workspaceFolders?.[0]?.uri.fsPath; // Use workspaceFolders
trae.window.showInputBox(options); // Simplified signature

// Command registration changes
// v2
trae.commands.registerCommand('command', callback);

// v3 (enhanced)
trae.commands.registerCommand('command', callback, thisArg);

Resources

Documentation

Tools

Community

Your Ultimate AI-Powered IDE Learning Guide