Plugin Development Guide

Everything you need to build, package, and distribute Electrum plugins.

Overview

Electrum plugins extend the wallet with new features without modifying core code. Plugins interact with Electrum through a hook system and can provide GUI elements for Qt, QML, and command-line interfaces.

Example plugin repositories for reference:

Plugin Hooks

Plugins interact with Electrum code through hooks. The @hook decorator on a plugin method registers it to be called when Electrum triggers that hook.

How hooks work

In your plugin class, decorate methods with @hook:

from electrum.plugin import BasePlugin, hook

class Plugin(BasePlugin):

    @hook
    def password_dialog(self, pw, grid, pos):
        """Called when the password dialog is displayed."""
        # Add custom UI elements to the password dialog
        ...

Electrum triggers hooks internally using run_hook():

# Inside Electrum core code
run_hook('password_dialog', pw, grid, pos)

When a hook is triggered, all enabled plugins that have a matching decorated method will be called.

Available hooks

Below is a reference of commonly used hooks. The hook name must match the method name exactly.

Hook Description
password_dialogCustomize the password input dialog
load_walletCalled when a wallet is loaded in the GUI
close_walletCalled when a wallet is being closed
daemon_wallet_loadedCalled when the daemon loads a wallet
init_menubarInitialize the application menu bar
init_qmlInitialize the QML application
init_wallet_wizardAdd pages to the wallet creation wizard
create_send_tabAdd elements to the send tab
create_status_barAdd elements to the status bar
abort_sendReturn True to abort sending a transaction
make_unsigned_transactionModify an unsigned transaction before signing
set_labelCalled when a wallet label is set
do_clearCalled when the send tab is cleared
on_close_windowCalled when a wallet window is closed
wallet_info_buttonsAdd buttons to the wallet information section
qt_utxo_menuAdd items to the UTXO context menu
receive_list_menuAdd items to the receive list context menu
receive_menuAdd items to the receive context menu
transaction_dialog_address_menuAdd items to the transaction dialog address menu
create_contact_menuAdd items to the contact context menu
export_history_to_fileCustom export of transaction history
close_settings_dialogCalled when the settings dialog is closed
show_xpub_buttonAdd buttons to the xpub display dialog
init_keystoreInitialize a custom keystore (hardware wallets)

Note: This list may be outdated. For a complete and up-to-date list of all available hooks, search the Electrum codebase for run_hook.

Directory Structure

A plugin is a directory containing the following files:

my_plugin/
├── manifest.json      # Plugin metadata (required)
├── __init__.py        # Commands and config declarations
├── qt.py              # Qt GUI module (optional)
├── qml.py             # QML GUI module (optional)
├── cmdline.py         # Command-line module (optional)
├── my_plugin.py       # Shared logic (optional)
├── icon.png           # Plugin icon (optional)
└── MyPlugin.qml       # QML UI file (optional)
  • manifest.json — describes the plugin and is pre-loaded to display plugin information in the Electrum client
  • __init__.py — registers commands and configuration variables
  • UI modules — contain a Plugin class inheriting from BasePlugin for each supported interface (Qt, QML, CLI)

Example: Labels plugin structure

labels/
├── manifest.json
├── __init__.py        # Plugin commands (push, pull)
├── labels.py          # Core sync logic
├── qt.py              # Qt GUI integration
├── qml.py             # QML GUI integration
├── cmdline.py         # CLI integration
├── Labels.qml         # QML UI component
└── labelsync.png      # Plugin icon

manifest.json

The manifest file is pre-loaded by Electrum to display plugin information before the plugin code itself is loaded. It must be valid JSON.

Example

{
    "name": "my_plugin",
    "fullname": "My Plugin",
    "description": "A brief description of what the plugin does.",
    "available_for": ["qt", "qml", "cmdline"],
    "author": "Your Name",
    "license": "MIT",
    "icon": "my-icon.png",
    "version": "0.0.1"
}

Field reference

Field Type Required Description
namestringYesInternal identifier (must match the directory name)
fullnamestringYesDisplay name shown to users
descriptionstringYesBrief description of plugin functionality
available_forarrayYesGUI types: "qt", "qml", "cmdline"
authorstringNoPlugin author name
licensestringNoSoftware license (e.g., MIT)
iconstringNoIcon filename (relative to plugin directory)
versionstringNoPlugin version string
requiresarrayNoExternal dependencies: [["package", "url"], ...]
registers_keystorearrayNoFor hardware wallets: ["hardware", "id", "Display Name"]
registers_wallet_typearrayNoFor custom wallet types

Hardware wallet manifest example

{
    "name": "coldcard",
    "fullname": "Coldcard Wallet",
    "description": "Provides support for the Coldcard hardware wallet from Coinkite",
    "requires": [["ckcc-protocol", "github.com/Coldcard/ckcc-protocol"]],
    "registers_keystore": ["hardware", "coldcard", "Coldcard Wallet"],
    "icon": "coldcard.png",
    "available_for": ["qt", "cmdline"]
}

Plugin Module

Each GUI module (e.g., qt.py) must define a Plugin class that inherits from BasePlugin. The class uses @hook decorators to register for events.

Minimal example

from electrum.plugin import BasePlugin, hook


class Plugin(BasePlugin):

    @hook
    def init_menubar(self):
        """Add a menu item when the menu bar is initialized."""
        print("My plugin is loaded!")

    def on_close(self):
        """Clean up when the plugin is disabled."""
        print("My plugin is closing.")

Full example with GUI elements

from electrum.plugin import BasePlugin, hook
from electrum.gui.qt.util import read_QIcon_from_bytes

from PyQt6.QtWidgets import QPushButton


class Plugin(BasePlugin):

    @hook
    def password_dialog(self, pw, grid, pos):
        """Add a virtual keyboard button to the password dialog."""
        vkb_button = QPushButton('')
        vkb_button.setIcon(
            read_QIcon_from_bytes(self.read_file("keyboard-icon.svg"))
        )
        vkb_button.clicked.connect(
            lambda: self.toggle_vkb(grid, pw)
        )
        grid.addWidget(vkb_button, pos, 2)

    def toggle_vkb(self, grid, pw):
        """Toggle the virtual keyboard visibility."""
        ...

Key BasePlugin methods

  • is_enabled() — check if the plugin is enabled and authorized
  • is_available() — override to add conditions for availability
  • can_user_disable() — return True (default) to allow the user to disable the plugin
  • on_close() — override for custom cleanup when the plugin is disabled
  • requires_settings() — return True if the plugin has a settings UI
  • settings_widget(window) — return a widget for the plugin settings dialog
  • read_file(filename) — read a file from the plugin directory (useful for icons and assets)
  • get_storage(wallet) — get persistent dict storage associated with a wallet
  • thread_jobs() — return a list of ThreadJob objects for background tasks

Plugin Commands

Plugins can register custom commands that are accessible from the Electrum CLI and daemon RPC interface.

Use the @plugin_command decorator from electrum.commands:

from electrum.commands import plugin_command

@plugin_command('w', 'labels')
async def push(self, plugin=None, wallet=None):
    """Push labels to the remote server."""
    return await plugin.push_thread(wallet)

@plugin_command('w', 'labels')
async def pull(self, plugin=None, wallet=None):
    """Pull labels from the remote server."""
    return await plugin.pull_thread(wallet)

The first argument to @plugin_command specifies the command category:

  • 'w' — wallet command (requires a loaded wallet)
  • 'n' — network command (requires network access)
  • 'wp' — wallet command requiring password

The second argument is the plugin name (must match manifest.json name).

Creating .zip Files

External plugins are distributed as .zip files that users import via the Electrum GUI.

Use the make_plugin script included in the Electrum source:

./contrib/make_plugin /path/to/my_plugin

This produces a file named my_plugin-0.0.1.zip (using the version from manifest.json).

Users can then import the zip file in Electrum via Tools → Plugins → Add.

Hardware Wallet Plugins

Hardware wallet plugins differ from standard plugins in several ways:

  • They are not displayed in the normal plugin list — they are enabled automatically by the wallet creation wizard
  • They use registers_keystore in manifest.json to register a hardware keystore type
  • They can be distributed as zip files with Python dependencies bundled
  • Non-Python dependencies (like hidapi) are typically bundled in Electrum binaries

Third-party hardware wallet plugins can be imported from the wallet wizard:

Electrum wallet creation wizard showing the Choose Hardware Device screen with Rescan devices and Add plugin buttons

Keystore registration

// In manifest.json
"registers_keystore": ["hardware", "my_device", "My Device Name"]

This tells Electrum to include your device in the hardware wallet selection during wallet creation.

Loading External Plugins

There are two ways to load external plugins:

1. Import from .zip file

Go to Tools → Plugins and click Add. Select a .zip file produced by the make_plugin script.

2. Symlink during development

When running Electrum from source, you can symlink your plugin directory into the plugins folder:

ln -s /path/to/my_plugin electrum/electrum/plugins/my_plugin

Electrum will detect and load the plugin on next startup.