Build a VS Code Extension with AI in 45 Minutes

Create a production-ready VS Code extension using Claude API for code refactoring. Includes setup, AI integration, and publishing steps.

Problem: Adding AI to Your Development Workflow

You want to integrate AI into VS Code but existing extensions don't fit your workflow. Building a custom extension seems complex, but with modern tools and AI APIs, you can ship one in under an hour.

You'll learn:

  • Set up a VS Code extension from scratch
  • Integrate Claude API for code analysis
  • Handle user input and editor interactions
  • Package and publish your extension

Time: 45 min | Level: Intermediate


Why Build Your Own Extension

Off-the-shelf AI extensions are generic. A custom extension lets you:

Benefits:

  • Tailor prompts to your coding style
  • Add team-specific refactoring rules
  • Control costs by caching responses
  • Integrate with your existing tools

Common use cases:

  • Code review automation
  • Custom snippet generation
  • Documentation writers
  • Test case generators

Prerequisites

# Check versions
node --version  # Need 18.x or higher
npm --version   # Need 9.x or higher

You'll need:

  • VS Code 1.85+ installed
  • Node.js 18+ and npm
  • Anthropic API key (get at console.anthropic.com)
  • 45 minutes of focused time

Solution

Step 1: Scaffold the Extension

# Install Yeoman and VS Code generator
npm install -g yo generator-code

# Generate extension
yo code

# Select these options:
# ? What type: New Extension (TypeScript)
# ? Name: ai-refactor-helper
# ? Identifier: ai-refactor-helper
# ? Description: AI-powered Code Refactoring
# ? Initialize git: Yes
# ? Package manager: npm

cd ai-refactor-helper

Expected: Creates project with package.json, src/extension.ts, and config files.

If it fails:

  • "yo: command not found": Run npm install -g yo generator-code again
  • EACCES error: Use sudo npm install -g on macOS/Linux

Step 2: Add Claude API Integration

# Install Anthropic SDK
npm install @anthropic-ai/sdk
npm install --save-dev @types/node

Update src/extension.ts:

import * as vscode from 'vscode';
import Anthropic from '@anthropic-ai/sdk';

let anthropic: Anthropic;

export function activate(context: vscode.ExtensionContext) {
  // Initialize Anthropic client
  const apiKey = vscode.workspace.getConfiguration('aiRefactor').get<string>('apiKey');
  
  if (!apiKey) {
    vscode.window.showErrorMessage('Set your Anthropic API key in settings');
    return;
  }
  
  anthropic = new Anthropic({ apiKey });

  // Register refactor command
  let disposable = vscode.commands.registerCommand(
    'ai-refactor-helper.refactorCode',
    async () => {
      const editor = vscode.window.activeTextEditor;
      if (!editor) {
        vscode.window.showWarningMessage('No active editor');
        return;
      }

      const selection = editor.selection;
      const selectedText = editor.document.getText(selection);

      if (!selectedText) {
        vscode.window.showWarningMessage('Select code to refactor');
        return;
      }

      await refactorWithAI(editor, selection, selectedText);
    }
  );

  context.subscriptions.push(disposable);
}

async function refactorWithAI(
  editor: vscode.TextEditor,
  selection: vscode.Selection,
  code: string
) {
  // Show progress
  await vscode.window.withProgress(
    {
      location: vscode.ProgressLocation.Notification,
      title: 'Refactoring with AI...',
      cancellable: false,
    },
    async () => {
      try {
        const message = await anthropic.messages.create({
          model: 'claude-sonnet-4-20250514',
          max_tokens: 2000,
          messages: [
            {
              role: 'user',
              content: `Refactor this code for better readability and performance. Return ONLY the refactored code, no explanations:

\`\`\`
${code}
\`\`\``,
            },
          ],
        });

        // Extract refactored code
        const refactoredCode = message.content[0].type === 'text' 
          ? message.content[0].text.replace(/```[\w]*\n?/g, '').trim()
          : '';

        // Replace selection
        await editor.edit((editBuilder) => {
          editBuilder.replace(selection, refactoredCode);
        });

        vscode.window.showInformationMessage('Code refactored successfully!');
      } catch (error) {
        vscode.window.showErrorMessage(`Refactor failed: ${error}`);
      }
    }
  );
}

export function deactivate() {}

Why this works:

  • withProgress shows user feedback during API calls
  • Prompt asks for code only (no markdown formatting to strip)
  • editor.edit properly handles undo/redo
  • Error handling prevents crashes on API failures

Step 3: Configure Extension Settings

Update package.json to add API key configuration:

{
  "name": "ai-refactor-helper",
  "displayName": "AI Refactor Helper",
  "description": "AI-powered Code Refactoring using Claude",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.85.0"
  },
  "categories": ["Other"],
  "activationEvents": [],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "ai-refactor-helper.refactorCode",
        "title": "Refactor Code with AI"
      }
    ],
    "configuration": {
      "title": "AI Refactor Helper",
      "properties": {
        "aiRefactor.apiKey": {
          "type": "string",
          "default": "",
          "description": "Your Anthropic API key (get from console.anthropic.com)",
          "markdownDescription": "Your Anthropic API key. Get one at [console.anthropic.com](https://console.anthropic.com)"
        }
      }
    },
    "keybindings": [
      {
        "command": "ai-refactor-helper.refactorCode",
        "key": "ctrl+shift+r",
        "mac": "cmd+shift+r",
        "when": "editorHasSelection"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "lint": "eslint src --ext ts"
  },
  "devDependencies": {
    "@types/node": "^18.x",
    "@types/vscode": "^1.85.0",
    "@typescript-eslint/eslint-plugin": "^6.x",
    "@typescript-eslint/parser": "^6.x",
    "eslint": "^8.x",
    "typescript": "^5.3.0"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.30.0"
  }
}

Key additions:

  • configuration section lets users set API key in VS Code settings
  • keybindings adds Ctrl+Shift+R (Cmd+Shift+R on Mac) shortcut
  • when clause ensures command only works when text is selected

Step 4: Test Your Extension

# Compile TypeScript
npm run compile

# Press F5 in VS Code to launch Extension Development Host
# Or run:
code --extensionDevelopmentPath=$(pwd)

In the Extension Development Host window:

  1. Open VS Code settings (Ctrl+,)
  2. Search for "AI Refactor"
  3. Paste your Anthropic API key
  4. Open any code file
  5. Select some code
  6. Press Ctrl+Shift+R or run "Refactor Code with AI" from Command Palette (Ctrl+Shift+P)

Expected: Selected code gets refactored and replaced in the editor.

If it fails:

  • "API key not set": Check Settings > Extensions > AI Refactor Helper
  • "No response": Check API key is valid at console.anthropic.com
  • Compile errors: Run npm install again to ensure dependencies installed

Step 5: Add Error Handling and Validation

Enhance src/extension.ts with better error handling:

// Add at the top
interface RefactorConfig {
  apiKey: string;
  maxTokens: number;
  temperature: number;
}

function getConfig(): RefactorConfig | null {
  const config = vscode.workspace.getConfiguration('aiRefactor');
  const apiKey = config.get<string>('apiKey', '');
  
  if (!apiKey || apiKey.length < 10) {
    vscode.window.showErrorMessage(
      'Invalid API key. Set it in Settings > AI Refactor Helper',
      'Open Settings'
    ).then(selection => {
      if (selection === 'Open Settings') {
        vscode.commands.executeCommand('workbench.action.openSettings', 'aiRefactor.apiKey');
      }
    });
    return null;
  }
  
  return {
    apiKey,
    maxTokens: config.get<number>('maxTokens', 2000),
    temperature: config.get<number>('temperature', 0.3),
  };
}

// Update activate function
export function activate(context: vscode.ExtensionContext) {
  let disposable = vscode.commands.registerCommand(
    'ai-refactor-helper.refactorCode',
    async () => {
      const config = getConfig();
      if (!config) return;
      
      // Initialize client per-request (handles key changes)
      anthropic = new Anthropic({ apiKey: config.apiKey });
      
      const editor = vscode.window.activeTextEditor;
      if (!editor) {
        vscode.window.showWarningMessage('No active editor');
        return;
      }

      const selection = editor.selection;
      const selectedText = editor.document.getText(selection);

      // Validate selection
      if (!selectedText || selectedText.length < 10) {
        vscode.window.showWarningMessage('Select at least 10 characters of code');
        return;
      }
      
      if (selectedText.length > 4000) {
        vscode.window.showWarningMessage('Selection too large (max 4000 chars). Select less code.');
        return;
      }

      await refactorWithAI(editor, selection, selectedText, config);
    }
  );

  context.subscriptions.push(disposable);
}

Why these checks matter:

  • API key validation prevents wasted API calls
  • Character limits control costs
  • "Open Settings" button improves UX
  • Per-request client init allows key changes without restart

Step 6: Package the Extension

# Install packaging tool
npm install -g @vscode/vsce

# Package extension
vsce package

# Creates: ai-refactor-helper-0.0.1.vsix

Expected: Creates a .vsix file you can install locally or publish.

To install locally:

# In VS Code
code --install-extension ai-refactor-helper-0.0.1.vsix

If it fails:

  • "Missing publisher": Add "publisher": "yourname" to package.json
  • "Missing LICENSE": Create LICENSE.txt or add "license": "MIT" to package.json
  • "Missing README": Add basic README.md explaining the extension

Step 7: Publish to VS Code Marketplace (Optional)

# Create publisher account at marketplace.visualstudio.com
# Get Personal Access Token from Azure DevOps

# Login
vsce login yourpublisher

# Publish
vsce publish

Before publishing:

  • Add icon.png (128x128) to project root
  • Write comprehensive README.md
  • Add screenshots showing the extension in action
  • Test on Windows, Mac, and Linux
  • Set up GitHub repository for issues

Publishing requirements:

  • Valid publisher account
  • Azure DevOps Personal Access Token
  • Icon and README
  • Unique extension name

Verification

Test the complete workflow:

  1. Install your extension
  2. Set API key in settings
  3. Open a TypeScript/JavaScript file
  4. Select a function
  5. Press Ctrl+Shift+R
  6. Verify code is refactored

You should see:

  • Progress notification appears
  • Selected code is replaced
  • Success message shows
  • Undo (Ctrl+Z) restores original code

Advanced: Add Multiple Refactor Options

Make the extension more useful with multiple AI commands:

// Add to package.json commands array
{
  "command": "ai-refactor-helper.explainCode",
  "title": "Explain Selected Code"
},
{
  "command": "ai-refactor-helper.addTests",
  "title": "Generate Tests for Code"
}

// Add to extension.ts
context.subscriptions.push(
  vscode.commands.registerCommand('ai-refactor-helper.explainCode', async () => {
    const { editor, selectedText } = getEditorAndSelection();
    if (!editor || !selectedText) return;
    
    const explanation = await callClaude(`Explain this code concisely:\n\n${selectedText}`);
    
    // Show in sidebar
    const panel = vscode.window.createWebviewPanel(
      'codeExplanation',
      'Code Explanation',
      vscode.ViewColumn.Beside,
      {}
    );
    panel.webview.html = `<html><body><pre>${explanation}</pre></body></html>`;
  })
);

context.subscriptions.push(
  vscode.commands.registerCommand('ai-refactor-helper.addTests', async () => {
    const { editor, selectedText } = getEditorAndSelection();
    if (!editor || !selectedText) return;
    
    const tests = await callClaude(
      `Generate Jest unit tests for this code:\n\n${selectedText}`
    );
    
    // Create new file with tests
    const doc = await vscode.workspace.openTextDocument({
      content: tests,
      language: 'typescript',
    });
    await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside);
  })
);

What You Learned

  • VS Code extensions use standard TypeScript and Node.js
  • Claude API integration takes ~20 lines of code
  • User settings are defined in package.json contributes.configuration
  • Progress notifications improve perceived performance
  • Proper error handling prevents user frustration

Limitations:

  • Claude API has rate limits (check console.anthropic.com)
  • Large code selections consume more tokens
  • Network latency affects responsiveness

When NOT to use this approach:

  • Real-time suggestions (use Language Server Protocol instead)
  • Offline functionality needed (bundle a local model)
  • Sub-second response required (cache or preprocess)

Cost Considerations

Typical usage:

  • Refactoring 100 lines: ~500 tokens input, ~150 tokens output
  • Cost per request: ~$0.001 (Claude Sonnet)
  • 1000 refactors/month: ~$1.00

Optimization tips:

  1. Cache common refactoring patterns
  2. Use prompt caching for system prompts
  3. Add token limits in settings
  4. Batch similar requests

Troubleshooting

Extension Not Loading

# Check logs
code --log-level trace

# Look for activation errors in Output > Extension Host

Common causes:

  • Syntax error in extension.ts
  • Missing dependency in package.json
  • Invalid activation event

API Calls Failing

// Add detailed logging
try {
  const message = await anthropic.messages.create({...});
  console.log('API Response:', message);
} catch (error) {
  console.error('API Error:', error);
  if (error instanceof Anthropic.APIError) {
    vscode.window.showErrorMessage(`API Error: ${error.message}`);
  }
}

Check:

  • API key is valid and has credits
  • Network connection works
  • Rate limits not exceeded

Command Not Appearing

Verify package.json:

  • Command ID matches code
  • Title is user-friendly
  • No typos in command name

Force reload:

Ctrl+Shift+P > Developer: Reload Window

Production Checklist

Before publishing:

  • Test on Windows, Mac, Linux
  • Add comprehensive README with screenshots
  • Include LICENSE file
  • Set up GitHub repository
  • Add issue templates
  • Write CHANGELOG.md
  • Add CI/CD for automated testing
  • Include icon.png (128x128)
  • Set semantic version (e.g., 1.0.0)
  • Test with large codebases
  • Verify all error messages are helpful
  • Add telemetry (optional, with opt-out)
  • Document configuration options
  • Create demo video

Resources

Official Documentation:

Example Extensions:

Tools:


Tested on VS Code 1.86.0, Node.js 20.11.0, @anthropic-ai/sdk 0.30.0, macOS Sonoma & Windows 11