Extension Development

Positron is compatible with VS Code extensions so you can create extensions as you would for VS Code. You can use Positron to develop your extension and run it in a new Extension Development Host window.

Prerequisites

To follow these docs and run the examples, you’ll need:

  • Basic familiarity with TypeScript/JavaScript and npm
  • An up-to-date version of Positron installed
  • Node.js (version 18 or greater)

These docs use the @posit-dev/positron npm package for type safety and IntelliSense.

The easiest way to follow along is to download the Positron extension template and add the examples to it as needed.

For more comprehensive setup instructions see the VS Code documentation on developing an extension.

What can you build?

The Positron API provides access to Positron-specific features like running code in active R or Python sessions, utilizing Positron’s preview pane, or contributing new database connections.

The following are the different sections of the API and how they’re used.

Namespace Typical use case
positron.window Preview URLs and HTML, monitor console layout
positron.runtime Execute Python or R code in the active console, enumerate sessions, inspect variables
positron.connections Add database / service drivers with custom credential UI
positron.languages Provide statement range & help topic providers for new languages
positron.methods Call low-level RPCs or show purpose-built dialogs
positron.environment Inspect environment variable contributions from all installed extensions

Setting up your extension

The easiest way to start an extension is to use the Positron extension template.

Alternatively, you can use VS Code’s extension template system to scaffold out a plain VS Code extension and then add Positron functionality to that by installing the Positron npm package.

npm install @posit-dev/positron

Key files

There are two files that are critical when setting up your extension.

Extension script (extension.ts)

import * as vscode from 'vscode';
import { tryAcquirePositronApi } from '@posit-dev/positron';

export function activate(context: vscode.ExtensionContext) {
    // Register a command
    const disposable = vscode.commands.registerCommand('myExtension.helloWorld', () => {
        const positron = tryAcquirePositronApi();
        
        if (positron) {
            vscode.window.showInformationMessage('Hello from Positron!');
            // Use Positron features here
        } else {
            vscode.window.showInformationMessage('Hello from VS Code!');
        }
    });
    
    context.subscriptions.push(disposable);
}

export function deactivate() {}

The activate() function is run when your extension is activated. It’s where you setup the code behind commands and other extension contributions.

Extension manifest (package.json)

{
    "name": "my-extension",
    "displayName": "My Extension",
    "version": "0.0.1",
    "engines": {
        "vscode": "^1.74.0"
    },
    "activationEvents": [],
    "main": "./out/extension.js",
    "contributes": {
        "commands": [
            {
                "command": "myExtension.helloWorld",
                "title": "Hello World"
            }
        ]
    },
    "dependencies": {
        "@posit-dev/positron": "^0.1.0"
    }
}

Your extension’s package.json file uses custom extension fields to declare what your extension contributes. It’s important to note that you need to add the contribution (in this case the command myExtension.helloWorld) to both the Typescript/Javascript code and the package.json.

Now users can run your command from the Command Palette (Cmd/Ctrl+Shift+P).

Detecting Positron

You have two ways to detect if your extension is running in Positron:

Option 1: Context keys

Use the isPositron context key in your extension manifest for simple detection:

"commands": [
    {
        "category": "My Extension",
        "command": "myExtension.myCommand",
        "title": "My Extension Command",
        "enablement": "isPositron"
    }
]

This enables commands, keybindings, and menu items to be visible only in Positron.

Option 2: Positron API

Detect Positron in your code:

import { tryAcquirePositronApi } from '@posit-dev/positron';

const positron = tryAcquirePositronApi();
if (positron) {
    // Positron features are available with full type safety
}

Alternatively, if you just need to check but don’t need the API you can use inPositron():

import { inPositron } from '@posit-dev/positron';

if (inPositron()) {
    // Behavior specific for Positron
}

Examples

Each example shows how to use a Positron feature inside a command.

Tip

Prefer to look at examples in a full extension context? Check out the Positron API showcase extension.

Display a web page

const disposable = vscode.commands.registerCommand('myExtension.showWebPage', () => {
    const positron = tryAcquirePositronApi();
    if (positron) {
        const url = vscode.Uri.parse('https://example.com');
        positron.window.previewUrl(url);
    }
});

Add this command to your package.json:

{
    "command": "myExtension.showWebPage",
    "title": "Show Web Page"
}

Run code in the console

Execute code in the active Python or R session:

const disposable = vscode.commands.registerCommand('myExtension.runCode', () => {
    const positron = tryAcquirePositronApi();
    if (positron) {
        // Run Python code
        positron.runtime.executeCode('python', 'print("Hello from extension!")', true);
        
        // Or run R code
        // positron.runtime.executeCode('r', 'print("Hello from R")', true);
    }
});

The third parameter (true) means the code appears in the console history.

Add a database connection

Register a custom database type with a working mock example:

// In your activate function
export function activate(context: vscode.ExtensionContext) {
    const positron = tryAcquirePositronApi();
    if (positron) {
        const disposable = positron.connections.registerConnectionDriver({
        driverId: 'mockdb',
        metadata: {
            languageId: 'python',
            name: 'Mock Database',
            inputs: [
                {
                    id: 'database',
                    label: 'Database Name',
                    type: 'string',
                    value: 'test_db'
                }
            ]
        },
        generateCode: (inputs) => {
            // If any of the inputs are an ID, use that as the name
            const dbName = inputs.find(i => i.id === 'database')?.value || 'test_db';
            return mockDatabaseCode(dbName);
        },
        connect: async (code) => {
            await positron.runtime.executeCode('python', code, true);
        }
    });
        context.subscriptions.push(disposable);
    }
}
Mock database code

In this example we simulate a database connection with a basic Python class. Swap this logic out for your use case!

// Mock database code generator. 
const mockDatabaseCode = (dbName: string) => `
# Mock database connection
class MockDatabase:
    def __init__(self, name):
        self.name = name
        self.tables = ['users', 'products', 'orders']
        print(f"Connected to mock database: {name}")
        print(f"Available tables: {', '.join(self.tables)}")
    
    def query(self, sql):
        return f"Mock result for: {sql}"

# Create connection
mock_db = MockDatabase("${dbName}")
print("\\nConnection successful! Try: mock_db.query('SELECT * FROM users')")
`;

The mockDatabaseCode function generates Python code that creates a simple mock database class. This lets you test the connection without installing a real database.

Advanced examples

These examples demonstrate more advanced integrations with Positron’s data science features:

Stream output with observers

Capture and process output from long-running computations as they execute:

import * as vscode from "vscode";

export async function runLongTask(code: string) {
  const positron = tryAcquirePositronApi();
  if (!positron) return;

  // Create an observer to handle output events
  const observer = {
    onStarted: () => vscode.window.showInformationMessage("Analysis started"),
    onOutput: (message) => {
      // Process output chunks as they arrive
      console.log("Output:", message.text || message);
    },
    onError: (error) => {
      console.error("Runtime error:", error);
      vscode.window.showErrorMessage(`Error: ${error.message}`);
    },
    onFinished: () => vscode.window.showInformationMessage("Analysis complete"),
  };

  // Execute code with streaming output
  await positron.runtime.executeCode(
    "python",
    code,
    true,    // show in console
    false,   // not an evaluation
    undefined,
    undefined,
    observer
  );
}

Use observers to process output progressively without blocking the UI. This approach works especially well for monitoring long-running analyses or streaming results.

Best practices

Performance

  • Batch API calls when possible; use getSessionVariables() instead of multiple individual lookups.
  • All API methods are asynchronous; always use await or handle promises appropriately.

Testing

Resource management

  • Clean up event listeners in your extension’s deactivate() function.
  • Add disposables to context.subscriptions for automatic cleanup.

Security

  • Set localResourceRoots when creating webviews.
  • Use content security policies for webviews that load external content.
  • Never expose sensitive data in webview messages.

Version compatibility

You can ensure your extension only activates in versions of Positron that you explicitly support by declaring a suitable version range in your package.json.

Positron versioning

Positron follows a calendar versioning scheme, while VS Code uses semantic versioning. Because of this, common range operators such as the caret (^) behave differently on each platform:

  • VS Code: major.minor.patch
    • A caret range stays within the current major version; ^1.74.0 expands to >=1.74.0 and <2.0.0.
    • Stepping up to 2.0.0 can therefore introduce breaking changes.
  • Positron: YYYY.M.P (year · month · patch)
    • Under calendar versioning, the caret simply means “this version or anything newer”; ^2025.6.0 translates to >=2025.6.0 with no upper bound.
    • Entering a new year (2026.x.x) is not automatically breaking—always consult the release notes.

Specifying Positron version requirements

Add a positron entry under the engines section of your package.json to declare the minimum build that your extension supports.

{
    "engines": {
        "vscode": "^1.74.0",    // VS Code 1.74.0 or later
        "positron": "^2025.6.0" // Positron 2025.6.0 or later
    }
}

@posit-dev/positron versioning

You can check the version compatibility table in the @posit-dev/positron package to check for any breaking changes you may need to be aware of.

Next steps

Resources

Get help

Tip

Start with the extension template to get a working setup quickly. Then add features one at a time as you learn the API.