This guide walks you through creating your first Verso extension, from setting up your development environment to loading the extension in dev mode.
- .NET 8 SDK or later
- A code editor (Visual Studio, VS Code, or Rider)
- Familiarity with C# and async/await
Verify your SDK installation:
dotnet --version
# Should output 8.0.x or higherInstall the Verso project template from NuGet:
dotnet new install Verso.TemplatesThis registers the verso-extension template. Confirm it is available:
dotnet new list versoYou should see a row for "Verso Extension" with short name verso-extension.
Create a new extension project:
dotnet new verso-extension -n MyDashboard \
--extensionId com.mycompany.dashboard \
--author "Your Name"| Option | Description | Default |
|---|---|---|
-n / --name |
Project and namespace name | MyExtension |
--extensionId |
Unique extension ID in reverse-domain format | com.example.myextension |
--author |
Author name embedded in the extension metadata | Extension Author |
--include-kernel |
Scaffold an ILanguageKernel implementation |
false |
--include-renderer |
Scaffold an ICellRenderer implementation |
false |
To include a language kernel and cell renderer:
dotnet new verso-extension -n DiceRoller \
--extensionId com.mycompany.diceroller \
--author "Your Name" \
--include-kernel \
--include-rendererAfter scaffolding, you will have this layout:
MyDashboard/
MyDashboard.cs # IExtension entry point marked with [VersoExtension]
SampleFormatter.cs # IDataFormatter scaffold
SampleToolbarAction.cs # IToolbarAction scaffold
SampleKernel.cs # (if --include-kernel) ILanguageKernel scaffold
SampleRenderer.cs # (if --include-renderer) ICellRenderer scaffold
GlobalUsings.cs # using Verso.Abstractions;
MyDashboard.csproj # Project file referencing Verso.Abstractions
MyDashboard.Tests/
SampleFormatterTests.cs
SampleToolbarActionTests.cs
GlobalUsings.cs
MyDashboard.Tests.csproj
The generated .csproj references the Verso.Abstractions NuGet package and enables GeneratePackageOnBuild:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>MyDashboard</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Description>A Verso extension.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Verso.Abstractions" Version="1.*" />
</ItemGroup>
</Project>Every extension needs at least one class implementing IExtension and decorated with [VersoExtension]. The template generates this for you:
[VersoExtension]
public sealed class MyDashboardEntry : IExtension
{
public string ExtensionId => "com.mycompany.dashboard";
public string Name => "MyDashboard";
public string Version => "1.0.0";
public string? Author => "Your Name";
public string? Description => "A Verso extension.";
public Task OnLoadedAsync(IExtensionHostContext context)
{
// Called when the extension is loaded by the host.
return Task.CompletedTask;
}
public Task OnUnloadedAsync()
{
// Called when the extension is unloaded.
return Task.CompletedTask;
}
}Classes that implement capability interfaces (ILanguageKernel, ICellRenderer, IDataFormatter, IToolbarAction, etc.) are each independently marked with [VersoExtension] and discovered separately. See Extension Interfaces for the full list.
Build both the extension and its test project:
dotnet build MyDashboard/
dotnet build MyDashboard/MyDashboard.Tests/The build output (DLL) will be in bin/Debug/net8.0/. Because GeneratePackageOnBuild is set, a .nupkg is also produced on each build.
The test project uses MSTest and references Verso.Testing for stub contexts:
dotnet test MyDashboard/MyDashboard.Tests/See Testing Extensions for details on writing tests with the stub and fake helpers.
To load your extension into a running Verso instance during development, add your build output as a local NuGet feed and install from it:
dotnet nuget add source ./MyDashboard/bin/Debug/ --name LocalExtensionsVerso will pick up the package when it resolves extensions. See Packaging and Publishing for the full NuGet workflow.
Verso discovers extensions by scanning assemblies for classes decorated with [VersoExtension]. Third-party extension packages are loaded into an isolated ExtensionLoadContext (a collectible AssemblyLoadContext) that shares only Verso.Abstractions types with the host. This ensures your extension's dependencies do not conflict with other extensions or with Verso itself. Built-in extensions that ship with Verso load in the default context without isolation.
The host calls OnLoadedAsync on each discovered IExtension instance, then categorizes it by the capability interfaces it implements (e.g., ILanguageKernel, ICellRenderer).
- Extension Interfaces -- reference for all extension interfaces
- Context Reference -- what each context object provides
- Testing Extensions -- writing unit tests with Verso.Testing
- Packaging and Publishing -- NuGet packaging workflow
- Best Practices -- naming, thread safety, and performance guidance
For a complete working example, see the Dice sample extension at samples/SampleExtension/Verso.Sample.Dice/.