Skip to content
Merged
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dd5e37b
Update extension development docs and add a more advanced version for…
nstrayer Jun 12, 2025
32b125f
Consolidate to a single doc
nstrayer Jun 12, 2025
49256af
Add note about engine declaration
nstrayer Jun 13, 2025
3a06923
Add section on positron engine versioning
nstrayer Jun 13, 2025
257d06c
Tweak intro text and add reference to the api template extension.
nstrayer Jun 16, 2025
e441e3a
Style compliance fixes and general edits.
nstrayer Jun 16, 2025
059894d
make vale linter happy
nstrayer Jun 16, 2025
43279a9
Maybe this will do it?
nstrayer Jun 16, 2025
ecc4ee2
Vale thinks that "Extension code" is not sentence case but "Extension…
nstrayer Jun 16, 2025
e47da3d
arbitrary change to trigger vale CLI again
nstrayer Jun 16, 2025
fd555fc
Edits for clarity
nstrayer Jun 16, 2025
4852143
Restructure to make the flow better
nstrayer Jun 16, 2025
7b9b060
Update extension-development.qmd
nstrayer Jun 17, 2025
dd937d5
Update extension-development.qmd
nstrayer Jun 17, 2025
be6e45e
Update extension-development.qmd
nstrayer Jun 17, 2025
d04bbf9
Update extension-development.qmd
nstrayer Jun 17, 2025
ae861bf
Update extension-development.qmd
nstrayer Jun 17, 2025
e025e85
Update extension-development.qmd
nstrayer Jun 17, 2025
bf9f18d
Update extension-development.qmd
nstrayer Jun 17, 2025
923adaf
Update extension-development.qmd
nstrayer Jun 17, 2025
7b9f194
Update extension-development.qmd
nstrayer Jun 17, 2025
55c9f81
Update extension-development.qmd
nstrayer Jun 17, 2025
e3fbd16
Update extension-development.qmd
nstrayer Jun 17, 2025
4b4c04d
Update extension-development.qmd
nstrayer Jun 17, 2025
e086db7
Update extension-development.qmd
nstrayer Jun 17, 2025
a68b8c0
Update extension-development.qmd
nstrayer Jun 17, 2025
83d3be7
Update extension-development.qmd
nstrayer Jun 17, 2025
a507b8c
Update extension-development.qmd
nstrayer Jun 17, 2025
2052544
Update extension-development.qmd
nstrayer Jun 17, 2025
94cf4e5
Update extension-development.qmd
nstrayer Jun 17, 2025
3cfdeea
Update extension-development.qmd
nstrayer Jun 17, 2025
ded4083
Update extension-development.qmd
nstrayer Jun 17, 2025
8236f26
Update extension-development.qmd
nstrayer Jun 17, 2025
78a0e63
Update extension-development.qmd
nstrayer Jun 17, 2025
047700e
Update extension-development.qmd
nstrayer Jun 17, 2025
88e4fbf
Updates from comments
nstrayer Jun 17, 2025
f28a20e
Copilot did good!
nstrayer Jun 17, 2025
651b67a
Update extension-development.qmd
nstrayer Jun 17, 2025
ba0676e
Update extension-development.qmd
nstrayer Jun 17, 2025
869ed07
Update extension-development.qmd
nstrayer Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
374 changes: 367 additions & 7 deletions extension-development.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,119 @@
title: "Extension Development"
---

Positron is compatible with VS Code extensions so you can create extensions [as you would for VS Code](https://code.visualstudio.com/api/get-started/your-first-extension). You can use Positron to develop your extension and run it in a new **Extension Development Host** window.
Positron is compatible with VS Code extensions so you can create extensions [as you would for VS Code](https://code.visualstudio.com/api/get-started/your-first-extension). You can use Positron to develop your extension and run it in a new **Extension Development Host** window

## Context keys
## Prerequisites

When defining your extension's manifest, you can use the `isPositron` context key for enablement or in a `when` clause.
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](https://www.npmjs.com/package/@posit-dev/positron) npm package for type safety and IntelliSense.

The easiest way to follow along is to download the [Positron extension template](https://github.com/posit-dev/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.](https://code.visualstudio.com/api/get-started/your-first-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](https://github.com/posit-dev/positron-extension-template).

Alternatively, you can use VS Code's [extension template system](https://code.visualstudio.com/api/get-started/your-first-extension) to scaffold out a plain VS Code extension and then add Positron functionality to that by installing the Positron npm package.

```bash
npm install @posit-dev/positron
```

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

#### Extension script (`extension.ts`)


```typescript
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.](https://code.visualstudio.com/api/references/activation-events)
It's where you setup the code behind commands and other extension contributions.

#### Extension manifest (`package.json`)

```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 extensions `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** (<kbd>Cmd/Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>).


### 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:

```json
"commands": [
Expand All @@ -19,10 +127,262 @@ When defining your extension's manifest, you can use the `isPositron` context ke
]
```

This allows your extension to enable commands, keybindings, menu items, and any other contribution points only for Positron.
This enables commands, keybindings, and menu items to be visible _only_ in Positron.

#### Option 2: Positron API

Detect Positron in your code:

```typescript
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()`

```typescript
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.

:::{.callout-tip}
Prefer to look at examples in a full extension context? Check out the [Positron API showcase extension.](https://github.com/posit-dev/positron-api-showcase)
:::

### Display a web page

```typescript
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:

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

### Run code in the console

Execute code in the active Python or R session:

```typescript
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:

```typescript
// 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);
}
}
```

<details>
<summary>Mock database code</summary>

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

```typescript
// 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.

</details>


### 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:

```typescript
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
* The [Positron extension template](https://github.com/posit-dev/positron-extension-template) has simple testing infrastructure setup for you that can be extended to fit your extension's needs.
* The [Positron showcase extension](https://github.com/posit-dev/positron-api-showcase) has a more fleshed out testing implementation.
* Refer to the [VS Code testing docs](https://code.visualstudio.com/api/working-with-extensions/testing-extension) for more information.

### 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.

```json
{
"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](https://github.com/posit-dev/positron-api-pkg?tab=readme-ov-file#version-compatibility) in the `@posit-dev/positron` package to check for any breaking changes you may need to be aware of.


## Next steps

### Resources

- Start quickly with the [Positron extension template](https://github.com/posit-dev/positron-extension-template).
- See more examples of the available API in the [showcase extension](https://github.com/posit-dev/positron-api-showcase).
- Complete type documentation is available on [npm](https://www.npmjs.com/package/@posit-dev/positron).
- Browse the source code of the [API package repository](https://github.com/posit-dev/positron-api-pkg).

## Extension APIs
### Get help

Positron provides [all the normal contribution points and the VS Code API](https://code.visualstudio.com/api/extension-capabilities/overview) to extensions. Unlike VS Code, however, so-called [proposed APIs](https://code.visualstudio.com/api/advanced-topics/using-proposed-api) are available to all extension authors by default, and don't require special configuration on the part of users to enable.
- Ask questions in [GitHub discussions](https://github.com/posit-dev/positron/discussions).
- File bugs in the [API repository](https://github.com/posit-dev/positron-api-pkg/issues).

Positron-native extensions can also make use of its own API. We plan to make the extension development experience better (for example, [safely wrapping](https://github.com/posit-dev/positron/issues/458) and [providing typing for](https://github.com/posit-dev/positron/issues/809) the Positron API), but in the meantime, we recommend you [take a look at the Positron API details directly](https://github.com/posit-dev/positron/tree/main/src/positron-dts).
::: {.callout-tip}
Start with the extension template to get a working setup quickly. Then add features one at a time as you learn the API.
:::