DEV Community

Cover image for Creating Your First VS Code Extension
Sanatan_dive
Sanatan_dive

Posted on

Creating Your First VS Code Extension

A Step-by-Step Guide

So you want to build your first VS Code extension? Awesome! Let's walk through this process together. I'll show you how to create a simple "Hello World" extension that displays a notification message when activated.

What We'll Build

We're going to create an extension that adds a command to VS Code. When you run the command, it'll show a friendly "Hello World" message. Simple, but it's the perfect starting point to understand how extensions work.

Prerequisites

Before we start, make sure you have:

  1. Node.js installed (version 14.x or higher)
  2. Git installed
  3. Visual Studio Code installed

Step 1: Install Yeoman and VS Code Extension Generator

First, we need to install some tools that will scaffold our project. Open your terminal and run:

npm install -g yo generator-code
Enter fullscreen mode Exit fullscreen mode

Yeoman (yo) is a scaffolding tool that creates project structures, and generator-code is a template specifically for VS Code extensions.

Step 2: Generate Your Extension Project

Now, let's create the extension scaffolding:

yo code
Enter fullscreen mode Exit fullscreen mode

You'll be asked several questions to set up your project:

  • Select "New Extension (TypeScript)"
  • Enter a name for your extension (e.g., "helloworld")
  • Enter an identifier (usually in format: publisher.extension-name)
  • Enter a description
  • Initialize a git repository? (recommended: Yes)

Step 3: Explore the Generated Project

After the generator finishes, you'll have a folder structure like this:

helloworld/
├── .vscode/
│   ├── launch.json     // Config for launching and debugging the extension
│   └── tasks.json      // Config for build task
├── node_modules/
├── src/
│   └── extension.ts    // Main extension code
├── .gitignore
├── package.json        // Extension manifest
├── tsconfig.json       // TypeScript configuration
└── README.md
Enter fullscreen mode Exit fullscreen mode

Let's look at the important files:

package.json

This is your extension's manifest file. It defines:

  • Metadata (name, description, version)
  • Activation events (when your extension loads)
  • Contribution points (what your extension adds to VS Code)

The default file looks something like this:

{
  "name": "helloworld",
  "displayName": "HelloWorld",
  "description": "My first extension",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.60.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "helloworld.helloWorld",
        "title": "Hello World"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src --ext ts"
  },
  "devDependencies": {
    "@types/vscode": "^1.60.0",
    "@types/node": "14.x",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.0",
    "typescript": "^4.3.5"
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice the contributes section defines a command called helloworld.helloWorld with the title "Hello World".

src/extension.ts

This file contains the main code for your extension:

// The module 'vscode' contains the VS Code extensibility API
import * as vscode from 'vscode';

// This method is called when your extension is activated
export function activate(context: vscode.ExtensionContext) {

    // Register a command that can be invoked via Command Palette
    let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World from HelloWorld!');
    });

    // Add to a list of disposables which are disposed when this extension is deactivated
    context.subscriptions.push(disposable);
}

// This method is called when your extension is deactivated
export function deactivate() {}
Enter fullscreen mode Exit fullscreen mode

Step 4: Understanding the Code

Let's break down what's happening:

  1. We import the VS Code API as vscode
  2. When the extension activates, the activate function runs
  3. Inside activate, we register a command called 'helloworld.helloWorld'
  4. When that command runs, it shows a notification with our message
  5. We add our command to the extension context's subscriptions for cleanup

Step 5: Run Your Extension

To test your extension:
Here’s your updated step-by-step guide with the additional commands:

  • Install dependencies: Open the terminal in VS Code and run:
   npm install prettier
   npm run compile
Enter fullscreen mode Exit fullscreen mode
  • Start the extension: Press F5 in VS Code.

  • Open the development host: A new "Extension Development Host" window will appear.

  • Access the Command Palette: In the new window, press Ctrl+Shift+P (or Cmd+Shift+P on Mac).

  • Run your command: Type "Hello World" and select your extension's command.

  • See the result: A message popup should appear with your notification!

after clicking ctrl+shift+p

output

Step 6: Making Changes

Let's modify our extension to make it more interesting. Update the extension.ts file:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    // Register our first command
    let helloCommand = vscode.commands.registerCommand('helloworld.helloWorld', () => {
        // Get the active text editor
        const editor = vscode.window.activeTextEditor;

        if (editor) {
            // If we have an editor, get the document and its text
            const document = editor.document;
            const selection = editor.selection;

            // Get the selected text or the word at the cursor position
            const word = document.getText(selection) || 
                         document.getText(document.getWordRangeAtPosition(selection.active) || selection);

            // Show a personalized greeting if text is selected
            if (word && word.trim().length > 0) {
                vscode.window.showInformationMessage(`Hello, ${word}!`);
            } else {
                vscode.window.showInformationMessage('Hello, World!');
            }
        } else {
            vscode.window.showInformationMessage('Hello, World!');
        }
    });

    // Register a second command that inserts text
    let insertCommand = vscode.commands.registerCommand('helloworld.insertHello', () => {
        const editor = vscode.window.activeTextEditor;

        if (editor) {
            // Insert text at the current cursor position
            editor.edit(editBuilder => {
                editBuilder.insert(editor.selection.active, 'Hello, VS Code Extension World!');
            });
        }
    });

    // Add both commands to subscriptions
    context.subscriptions.push(helloCommand, insertCommand);
}

export function deactivate() {}
Enter fullscreen mode Exit fullscreen mode

Now we need to update our package.json to include the new command:

"contributes": {
  "commands": [
    {
      "command": "helloworld.helloWorld",
      "title": "Hello World"
    },
    {
      "command": "helloworld.insertHello",
      "title": "Insert Hello Message"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Adding a Keybinding

Let's add a keyboard shortcut for our insert command. In package.json, add:

"contributes": {
  "commands": [...],
  "keybindings": [
    {
      "command": "helloworld.insertHello",
      "key": "ctrl+alt+h",
      "mac": "cmd+alt+h",
      "when": "editorTextFocus"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Step 8: Adding Extension Settings

Let's make our greeting customizable through settings:

In package.json, add:

"contributes": {
  "commands": [...],
  "keybindings": [...],
  "configuration": {
    "title": "Hello World",
    "properties": {
      "helloworld.greeting": {
        "type": "string",
        "default": "Hello",
        "description": "The greeting to use"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then update extension.ts to use this setting:

// In the helloWorld command handler:
const config = vscode.workspace.getConfiguration('helloworld');
const greeting = config.get('greeting') || 'Hello';

if (word && word.trim().length > 0) {
    vscode.window.showInformationMessage(`${greeting}, ${word}!`);
} else {
    vscode.window.showInformationMessage(`${greeting}, World!`);
}
Enter fullscreen mode Exit fullscreen mode

Create a New File to

after clicking ctrl+shift+p

output

Step 9: Package Your Extension

Once you're happy with your extension, you can package it for distribution:

  1. Install the VSCE packaging tool:
npm install -g vsce
Enter fullscreen mode Exit fullscreen mode
  1. Package your extension:
vsce package
Enter fullscreen mode Exit fullscreen mode

This creates a .vsix file that can be installed in VS Code.

Step 10: Installing Your Extension Manually

To install your packaged extension:

  1. Open VS Code
  2. Go to Extensions view (Ctrl+Shift+X)
  3. Click the "..." at the top of the Extensions view
  4. Select "Install from VSIX..."
  5. Choose your .vsix file

Complete Project Example

Here's a complete example of what your final files might look like:

package.json

{
  "name": "helloworld",
  "displayName": "HelloWorld",
  "description": "A friendly hello world extension",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.60.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "helloworld.helloWorld",
        "title": "Say Hello"
      },
      {
        "command": "helloworld.insertHello",
        "title": "Insert Hello Message"
      }
    ],
    "keybindings": [
      {
        "command": "helloworld.insertHello",
        "key": "ctrl+alt+h",
        "mac": "cmd+alt+h",
        "when": "editorTextFocus"
      }
    ],
    "configuration": {
      "title": "Hello World",
      "properties": {
        "helloworld.greeting": {
          "type": "string",
          "default": "Hello",
          "description": "The greeting to use"
        }
      }
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src --ext ts",
    "test": "node ./out/test/runTest.js"
  },
  "devDependencies": {
    "@types/vscode": "^1.60.0",
    "@types/glob": "^7.1.3",
    "@types/mocha": "^8.2.2",
    "@types/node": "14.x",
    "eslint": "^7.27.0",
    "glob": "^7.1.7",
    "mocha": "^8.4.0",
    "typescript": "^4.3.2",
    "vscode-test": "^1.5.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

extension.ts

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    console.log('Congratulations, your extension "helloworld" is now active!');

    // Register our hello command
    let helloCommand = vscode.commands.registerCommand('helloworld.helloWorld', () => {
        // Get user settings
        const config = vscode.workspace.getConfiguration('helloworld');
        const greeting = config.get('greeting') || 'Hello';

        // Get the active text editor
        const editor = vscode.window.activeTextEditor;

        if (editor) {
            // Get selected text if any
            const document = editor.document;
            const selection = editor.selection;

            const word = document.getText(selection) || 
                         document.getText(document.getWordRangeAtPosition(selection.active) || selection);

            if (word && word.trim().length > 0) {
                vscode.window.showInformationMessage(`${greeting}, ${word}!`);
            } else {
                vscode.window.showInformationMessage(`${greeting}, World!`);
            }
        } else {
            vscode.window.showInformationMessage(`${greeting}, World!`);
        }
    });

    // Register our insert command
    let insertCommand = vscode.commands.registerCommand('helloworld.insertHello', () => {
        // Get user settings
        const config = vscode.workspace.getConfiguration('helloworld');
        const greeting = config.get('greeting') || 'Hello';

        const editor = vscode.window.activeTextEditor;

        if (editor) {
            // Insert text at current cursor position
            editor.edit(editBuilder => {
                editBuilder.insert(editor.selection.active, `${greeting}, VS Code Extension World!`);
            });

            // Show a confirmation message
            vscode.window.showInformationMessage('Text inserted successfully!');
        } else {
            vscode.window.showWarningMessage('No active editor found');
        }
    });

    // Add commands to the extension context
    context.subscriptions.push(helloCommand, insertCommand);
}

export function deactivate() {
    console.log('Your extension "helloworld" is now deactivated!');
}
Enter fullscreen mode Exit fullscreen mode

Going Further

Once you're comfortable with the basics, you can explore more advanced features:

  • Creating a status bar item
  • Adding a view container and TreeView
  • Creating a webview
  • Adding language support features
  • Working with workspace files
  • Adding diagnostic information

Troubleshooting Tips

  • If your extension isn't showing up, make sure the activation events are correctly set
  • Check the Developer Console (Help > Toggle Developer Tools) for error messages
  • Use console.log() to debug (output appears in the Debug Console)
  • If you update package.json, you often need to reload the extension (press F5 again)

That's it! You've created your first VS Code extension.

Top comments (4)

Collapse
 
wouffle profile image
Shivangi Sharma

Awesome

Collapse
 
sanatan_dive profile image
Sanatan_dive

Thank youuu

Collapse
 
bluedv profile image
Aman Saini

great >

Collapse
 
sanatan_dive profile image
Sanatan_dive

Thank you jii