# Plugins
BTCPay Server Plugins are written in C#. They extend the core functionality and run in the same context as the BTCPay Server code. As a prerequisite for developing a plugin, you should be familiar with the local development process.
# Setup of a new plugin
Create a folder for the BTCPay Server projects. It will contain at least:
- Your plugin repository
- Your fork of the BTCPay Server repository
You can get started by cloning the plugin template (opens new window) or taking a look at existing plugins. This tutorial uses the plugin template as an example — substitute the references with your own plugin or simply follow along with the template.
The plugin repository should have BTCPay Server as a submodule. This way you are able to reference BTCPay Server as a dependency, so that you can use the existing core classes and modules. Start by first building BTCPay Server and then your plugin to check that the references are working:
# Clone the plugin template to a new directory called btcpayserver-my-plugin + make sure we get the contents of the submodule too
git clone git@github.com:btcpayserver/btcpayserver-plugin-template.git --recurse-submodules btcpayserver-my-plugin
# Enter the dir
cd btcpayserver-my-plugin
# Build the BTCPay Server project inside the plugin repository
dotnet build btcpayserver
# Build your plugin, which references the BTCPay Server project
dotnet build BTCPayServer.Plugins.Template
To develop your plugin you will need the BTCPay Server solution as the context: Fork the main repository (opens new window) to your personal GitHub account and clone it onto your computer.
The folder structure should now look like this:
|_ btcpayserver # your fork
|_ btcpayserver-plugin-template
|_ btcpayserver # the submodule
|_ BTCPayServer.Plugins.Template
Before starting, rename BTCPayServer.Plugins.Template
to the name of your plugin.
Also rename the BTCPayServer.Plugins.Template/BTCPayServer.Plugins.Template.csproj
file.
In the csproj file, customize the plugin information, for example:
<PropertyGroup>
<Product>Cool Plugin</Product>
<Description>My plugin is doing nothing, but it's cool.</Description>
<Version>1.0.0</Version>
</PropertyGroup>
# Plugin reference
In the forked repository you can include your plugin in the solution (opens new window) inside the Plugins
subdirectory (opens new window):
# Enter the forked BTCPay Server repository
cd btcpayserver
# Add your plugin to the solution
dotnet sln add ../btcpayserver-plugin-template/BTCPayServer.Plugins.Template -s Plugins
This references the plugin project in the folder, that sits right next to your BTCPay Server fork.
The BTCPay Server dependency
Your plugin is part of the BTCPay Server solution then, but keep the following in mind: The BTCPay Server version your plugin has as a dependecy is the submodule in the plugin repository — not the one in the forked repository. You will need to update the submodule to access the latest version of BTCPay Server.
To have the main project include the plugin whenever you run the app in development mode, you need to add the file BTCPayServer/appsettings.dev.json
. It is ignored in the repository and references the local and built version of your plugin for debugging:
{
"DEBUG_PLUGINS": "/absolute/path/btcpayserver-plugin-template/BTCPay.Plugins.Template/bin/Debug/net6.0/BTCPayServer.Plugins.Template.dll"
}
You need to reference the built DLL file with the absolute path of the build version of your plugin on your local file system. If you want to reference multiple plugins, separate them using a semicolon.
Once that is set up, you should be able to build and run the app — see the startup message for potential problems. Your plugin should be included and also be ready for debugging.
Building the whole solution
You might want to setup a pre-build step in the solution, so that your plugins gets rebuild whenever you run the app. Do so by editing the run/debug configuration and choose the build the whole solution, instead of just the BTCPay Server project.
# Coding a plugin
More information on the following topics will be provided soon. For now, these are the basics you should know about …
# Assets
In order to reference assets (CSS, JavaScript and images), the plugin project needs to embed a Resources
folder (opens new window) like this:
<ItemGroup>
<ProjectReference Include="..\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
<EmbeddedResource Include="Resources\**" />
</ItemGroup>
Then you can reference the assets in your views like this:
<img src="~/Resources/img/my.png" asp-append-version="true" />
<script src="~/Resources/js/my.js" asp-append-version="true"></script>
<link
href="~/Resources/css/my.css"
asp-append-version="true"
rel="stylesheet"
/>
A good example of this is the Bitcoin Whitepaper plugin (opens new window) which exposes the bitcoin whitepaper PDF on your BTCPay Server using embedded resources.
# Database
The main BTCPay Server database tables are part of the public schema. Plugins have their own database context and schema, named after the plugin:
public class MyPluginDbContextFactory : BaseDbContextFactory<MyPluginDbContext>
{
public MyPluginDbContextFactory(IOptions<DatabaseOptions> options) :
base(options, "BTCPayServer.Plugins.Template") {}
}
Plugins can have their own data models and migrations:
# Add a new migration once you defined a new model or updates
dotnet ef migrations add MoreData -p BTCPayServer.Plugins.Template -c PluginDbContext -o Data/Migrations
# Update the database
dotnet ef database update -p BTCPayServer.Plugins.Template -c PluginDbContext
When inspecting the database (with psql
), only the tables of the public schema are shown by default.
If you want to also see and select the plugin tables, you need to extend the search path:
# list plugin schemas
SELECT * FROM pg_catalog.pg_namespace WHERE nspname LIKE 'BTCPayServer.%';
# extend search path
SET search_path TO "BTCPayServer.Plugins.Template", public;
# table list now also shows the template plugin tables
\dt
# UI Extension Points
The extension points offer you ways to add your plugin views and partials to the UI. They get defined in the plugin base class. The following snippet shows how you can add a link to your plugin to the main navigation:
public class Plugin : BaseBTCPayServerPlugin
{
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IUIExtension>(new UIExtension("TemplatePluginHeaderNav", "header-nav"));
}
}
In this case, header-nav
is the name of the extension point.
You can find the available extension points by searching for the vc:ui-extension-point
references inside the main app.
For the header-nav
the reference looks like this:
<vc:ui-extension-point location="header-nav" model="@Model" />
The views and partials (i.e. TemplatePluginHeaderNav.cshtml
) need to be located in the Shared
folder of your Views
or Pages
directory, so that the main app can find and include them.
Missing extension points
If you would like to extend the UI but an extension point is not available yet, feel free to open an issue with a request to add it. We extend them as we move along, same with actions and filters …
# Actions and Filters
In addtion to the extention points which hook into the UI, you can also use the following hooks to modify and extend behaviour:
- Action (opens new window): Extend the core functionality
- Filters (opens new window): Do something and also return data
As with the UI extention points, you can define them within the Execute
method of the plugin base class:
public class Plugin : BaseBTCPayServerPlugin
{
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IPluginHookAction, MyPluginAction>();
services.AddSingleton<IPluginHookFilter, MyPluginFilter>();
}
}
Find the available hooks by searching for ApplyAction
and ApplyFilter
calls inside the main app.
# Authorization and Permissions
You can reuse the AuthenticationSchemes
and Policies
of the main app:
// Authorize users via their cookie login
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)]
public class UIPluginController : Controller
{
// GET might inherit CanViewProfile
[HttpGet("")]
public async Task<IActionResult> Index()
{
return View();
}
// POST might require CanModifyProfile
[HttpPost("update")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyProfile)]
public async Task<IActionResult> Modify()
{
return RedirectToAction(nameof(Index))
}
}
To show certain parts of the UI depending on the permissions the user has, you can use the permissions
view tag helper:
<li class="nav-item" permission="@Policies.CanModifyProfile"></li>
# Customizing Authorization
You can also define your own AuthenticationSchemes
and Policies
within the Execute
method of the plugin base class:
public class Plugin : BaseBTCPayServerPlugin
{
public override void Execute(IServiceCollection services)
{
// Add custom authentication scheme
var builder = new AuthenticationBuilder(services);
builder.AddScheme<PluginAuthenticationOptions, PluginAuthenticationHandler>(
PluginAuthenticationSchemes.AccessKey, _ => { });
// Add custom policies
services.AddAuthorization(opts =>
{
foreach (var policy in PluginPolicies.AllPolicies)
{
opts.AddPolicy(policy, policyBuilder => policyBuilder
.AddRequirements(new PolicyRequirement(policy)));
}
});
}
}
The custom policies might look like this:
public class PluginPolicies
{
public const string CanViewWallet = "btcpay.plugin.template.canviewwallet";
public const string CanManageWallet = "btcpay.plugin.template.canmanagewallet";
public static IEnumerable<string> AllPolicies
{
get
{
yield return CanViewWallet;
yield return CanManageWallet;
}
}
}
# API
In case your plugin has an API and you want to add its OpenAPI documentation, add a class inheriting from our ISwaggerProvider
:
public class PluginSwaggerProvider : ISwaggerProvider
{
private readonly IFileProvider _fileProvider;
public PluginSwaggerProvider(IWebHostEnvironment webHostEnvironment)
{
_fileProvider = webHostEnvironment.WebRootFileProvider;
}
public async Task<JObject> Fetch()
{
JObject json = new();
var fi = _fileProvider.GetFileInfo("Resources/swagger/v1/swagger.template.plugin.json");
await using var stream = fi.CreateReadStream();
using var reader = new StreamReader(fi.CreateReadStream());
json.Merge(JObject.Parse(await reader.ReadToEndAsync()));
return json;
}
}
As you can see it references the Swagger files in Resources/swagger/v1
— you can add them just like other assets.
Once that is done, your plugin API documentation should appear on the instance /docs
path alongside the Greenfield API documentation (opens new window).
# Publishing the plugin
The plugins are published via the plugin builder (opens new window). You can sign up, build and submit new versions of your plugin using this web UI.
Once you have a new version ready, you can create a new build. To do so, you will need to reference the Git repository of your plugin, as well as the branch and path of your plugin.
The result will be a packaged version of your plugin in prerelease
state.
A version in prerelease can be modified just by rebuilding your plugin in the plugin builder.
You can browse the prereleased plugin list on any BTCPay Server by going to Server Settings > Policies
, check Show plugins in pre-release
and Save
.
Once you click the Release
button on the build page, the package won't be in prerelease anymore and it is visible to everyone. Once the package is released, you won't be able to publish a new build with the same version number. So you will need to bump the <Version>
of your plugin in the csproj before publishing any new adjustment to your plugin.
# Resources
For more information check out these repositories with existing plugins: