Skip to main content
Workbench contributions are the primary way to extend VS Code’s functionality. They are classes that get instantiated at specific lifecycle phases and can register UI components, commands, keybindings, and more.

What are Contributions?

A workbench contribution is a class that:
  • Implements IWorkbenchContribution interface
  • Is instantiated at a specific lifecycle phase
  • Uses dependency injection to access services
  • Registers functionality like commands, views, or event handlers
  • Is automatically disposed when the workbench shuts down
Contributions are similar to extension activation, but they run in the core workbench process and have access to internal APIs.

Contribution Types

1. Phase-Based Contributions

Most contributions are loaded at a specific lifecycle phase:

2. Lazy Contributions

Contributions that load only when explicitly requested:

3. Editor-Specific Contributions

Contributions that load when a specific editor type is opened:

Creating a Contribution

Basic Contribution

Registering a Contribution

Use the new registerWorkbenchContribution2 API:
Always provide a unique ID for contributions to enable tracking and debugging.

Real-World Examples

Example 1: Debug Contribution

From the Debug feature:
The Debug feature registers multiple contributions at different phases:
  • Eventually: Status and progress indicators (non-critical)
  • Restored: Debug toolbar (visible immediately)
  • AfterRestored: Chat integration (after UI is ready)

Example 2: View Container Registration

Registering a custom view container:

Example 3: Command Registration

Registering commands in a contribution:

Contribution Patterns

Pattern 1: Event Handler Contribution

React to workbench events:

Pattern 2: Configuration Listener

Respond to configuration changes:

Pattern 3: Lifecycle-Aware Contribution

Perform actions at specific lifecycle events:

Choosing the Right Phase

BlockStartup

Use when:
  • Absolutely critical for workbench initialization
  • Must run before any UI is shown
  • Very rare - avoid if possible
Example: Core error handler setup

BlockRestore

Use when:
  • Required for UI restoration
  • Must run before editors are shown
  • Still blocks UI - use sparingly
Example: Layout service initializationUse when:
  • Most feature contributions
  • Can wait until UI is visible
  • Doesn’t impact initial load time
Example: Command registration, view providers, menu items

Eventually

Use when:
  • Non-critical features
  • Can load in background
  • Doesn’t affect user experience if delayed
Example: Telemetry, background analyzers, optional integrations

Lazy Contributions

For features that should only load when needed:

Editor-Specific Contributions

Load contributions only when specific editor types open:
This contribution only loads when a Jupyter notebook is opened.

Best Practices

  1. Choose the Right Phase: Use AfterRestored or Eventually unless you have a specific reason
  2. Keep Constructors Light: Move heavy initialization to async methods
  3. Dispose Properly: Always extend Disposable and use _register()
  4. Use Unique IDs: Provide descriptive, unique IDs for all contributions
  5. Avoid Side Effects: Don’t perform actions that modify global state in constructor
  6. Lazy Load When Possible: Use lazy contributions for optional features
  7. Handle Errors Gracefully: Wrap initialization in try-catch to avoid breaking workbench
  8. Document Dependencies: Clearly document which services your contribution requires

Debugging Contributions

VS Code provides tools to debug contribution loading:

Migration from Old API

If you’re using the deprecated API:

Next Steps

Views and Panels

Learn how to create custom views in contributions

Commands and Menus

Register commands and menu items in contributions