Skip to main content
KiCad supports Python plugins that extend the functionality of the PCB editor. There are two main types of plugins: Action Plugins and Footprint Wizard Plugins.

Plugin Types

Action Plugins

Action plugins add custom commands to the PCB editor menu and toolbar. They can modify the board, perform calculations, or interact with external tools.

Footprint Wizard Plugins

Footprint wizards generate parametric footprints based on user-defined parameters.

Action Plugins

Basic Structure

Create a class that inherits from pcbnew.ActionPlugin:
import pcbnew

class MyActionPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        """Define plugin metadata"""
        self.name = "My Plugin Name"
        self.category = "Modify PCB"
        self.description = "Description of what this plugin does"
        self.show_toolbar_button = True
        self.icon_file_name = "path/to/icon.png"
        self.dark_icon_file_name = "path/to/dark_icon.png"
    
    def Run(self):
        """Plugin execution - called when user activates the plugin"""
        board = pcbnew.GetBoard()
        # Your plugin logic here
        pcbnew.Refresh()

# Register the plugin
MyActionPlugin().register()

ActionPlugin Class Reference

Required Methods

defaults()
Defines plugin metadata. Must set:
  • self.name (str) - Display name in menu
  • self.category (str) - Menu category/submenu
  • self.description (str) - Detailed description
Run()
Executed when the plugin is activated. Contains the main plugin logic.
def Run(self):
    board = pcbnew.GetBoard()
    # Modify board or perform operations
    pcbnew.Refresh()  # Update display

Optional Properties

show_toolbar_button (bool)
If True, adds a button to the top toolbar. Default: False
icon_file_name (str)
Path to icon file for light theme (PNG format recommended).
dark_icon_file_name (str)
Path to icon file for dark theme. Falls back to icon_file_name if not set.

Inherited Methods

GetName() → str
Returns the plugin name.
GetCategoryName() → str
Returns the category name.
GetDescription() → str
Returns the plugin description.
GetClassName() → str
Returns the Python class name.
GetShowToolbarButton() → bool
Returns whether toolbar button is shown.
GetIconFileName(dark) → str
Returns the appropriate icon file path. Parameters:
  • dark (bool) - If True, returns dark theme icon
GetPluginPath() → str
Returns the path where the plugin was loaded from.

Plugin Discovery

KiCad searches for plugins in these locations:
  1. Bundled plugins: <kicad_installation>/scripting/plugins/
  2. User plugins: <user_config>/scripting/plugins/
  3. Third-party plugins: $KICAD_3RD_PARTY/plugins/
Plugins must either:
  • Be a .py file (e.g., my_plugin.py)
  • Be a directory containing __init__.py (e.g., my_plugin/__init__.py)

Example: Add Date to PCB

import pcbnew
import re
import datetime

class AddDatePlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Add Date on PCB"
        self.category = "Modify PCB"
        self.description = "Automatically add date to text fields marked with $date$"
    
    def Run(self):
        pcb = pcbnew.GetBoard()
        
        for draw in pcb.GetDrawings():
            if draw.GetClass() == 'PTEXT':
                # Find $date$ markers and replace with current date
                txt = re.sub(r"\$date\$ [0-9]{4}-[0-9]{2}-[0-9]{2}",
                           "$date$", draw.GetText())
                
                if txt == "$date$":
                    draw.SetText(f"$date$ {datetime.date.today()}")
        
        pcbnew.Refresh()

AddDatePlugin().register()

Example: Automatic Border

This plugin adds a border around the board edge:
import pcbnew

class AutoBorderPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Add Automatic Border"
        self.category = "Modify PCB"
        self.description = "Add a border around the board edge"
        self.show_toolbar_button = True
    
    def Run(self):
        board = pcbnew.GetBoard()
        
        # Get board bounding box
        bbox = board.GetBoardEdgesBoundingBox()
        
        # Add margin (2mm)
        margin = pcbnew.FromMM(2.0)
        bbox.Inflate(margin)
        
        # Create rectangle on Dwgs.User layer
        rect = pcbnew.PCB_SHAPE(board)
        rect.SetShape(pcbnew.SHAPE_T_RECT)
        rect.SetStart(bbox.GetPosition())
        rect.SetEnd(bbox.GetEnd())
        rect.SetLayer(pcbnew.Dwgs_User)
        rect.SetWidth(pcbnew.FromMM(0.1))
        
        board.Add(rect)
        pcbnew.Refresh()

AutoBorderPlugin().register()

Footprint Wizard Plugins

Basic Structure

import pcbnew

class MyFootprintWizard(pcbnew.FootprintWizardPlugin):
    def __init__(self):
        pcbnew.FootprintWizardPlugin.__init__(self)
        self.name = "My Footprint Wizard"
        self.description = "Creates custom footprints"
        self.image = "path/to/preview.png"
    
    def GetName(self):
        return self.name
    
    def GetDescription(self):
        return self.description
    
    def GetValue(self):
        return "Custom_FP"
    
    def GenerateParameterList(self):
        # Define wizard parameters
        self.AddParam("Pads", "pad count", pcbnew.uInteger, 8)
        self.AddParam("Pads", "pad width", pcbnew.uMM, 1.5)
        self.AddParam("Pads", "pad height", pcbnew.uMM, 2.0)
        self.AddParam("Pads", "pad pitch", pcbnew.uMM, 2.54)
    
    def CheckParameters(self):
        # Validate parameters
        pass
    
    def BuildFootprint(self):
        # Generate footprint based on parameters
        prm = self.parameters
        pad_count = prm["Pads"]["pad count"]
        pad_width = prm["Pads"]["pad width"]
        pad_height = prm["Pads"]["pad height"]
        pad_pitch = prm["Pads"]["pad pitch"]
        
        # Create footprint
        self.module = pcbnew.FOOTPRINT(None)
        self.module.SetReference("U")
        self.module.SetValue(self.GetValue())
        
        # Add pads, silkscreen, etc.
        # ...

MyFootprintWizard().register()

Parameter Types

Use these constants for parameter units:
  • pcbnew.uMM - Millimeters
  • pcbnew.uMils - Mils (thousandths of an inch)
  • pcbnew.uFloat - Floating point number
  • pcbnew.uInteger - Integer
  • pcbnew.uBool - Boolean
  • pcbnew.uRadians - Radians
  • pcbnew.uDegrees - Degrees
  • pcbnew.uPercent - Percentage
  • pcbnew.uString - String

FootprintWizardParameter Class

Parameters are defined using:
self.AddParam(page, name, unit, default, **kwargs)
Parameters:
  • page (str) - Parameter page/group name
  • name (str) - Parameter name
  • unit (str) - Unit type (from constants above)
  • default - Default value
  • hint (str, optional) - Tooltip text
  • designator (str, optional) - Short designator (e.g., “e”, “D”)
  • min_value (optional) - Minimum allowed value
  • max_value (optional) - Maximum allowed value
  • multiple (int, optional) - For integers, value must be multiple of this

Accessing Parameters

Use the parameters property to access parameter values:
def BuildFootprint(self):
    prm = self.parameters
    
    # Access by page and name
    pad_count = prm["Pads"]["pad count"]
    pad_width = prm["Pads"]["pad width"]
Values are automatically converted to internal units (nanometers).

Plugin Loading

KiCad loads plugins at startup using the LoadPlugins() function:
pcbnew.LoadPlugins(bundlepath, userpath, thirdpartypath)
This function:
  1. Searches plugin directories
  2. Imports Python modules
  3. Tracks dependencies
  4. Handles errors and reports failures

Plugin Reload

Plugins are automatically reloaded when:
  • Modified files are detected
  • KiCad restarts
  • Forced reload is triggered

Error Handling

Failed plugins are tracked in:
pcbnew.NOT_LOADED_WIZARDS  # List of failed plugin paths
pcbnew.FULL_BACK_TRACE     # Error traces
Query these for debugging:
failed = pcbnew.GetUnLoadableWizards()
traces = pcbnew.GetWizardsBackTrace()

Best Practices

1. Error Handling

Wrap plugin code in try-except blocks:
def Run(self):
    try:
        board = pcbnew.GetBoard()
        # Plugin logic
    except Exception as e:
        import traceback
        print(f"Plugin error: {e}")
        traceback.print_exc()

2. Avoid Print Statements

Print statements can cause I/O exceptions when KiCad runs without a console. Use logging or write to files instead.

3. Refresh After Modifications

Always call pcbnew.Refresh() after modifying the board:
def Run(self):
    board = pcbnew.GetBoard()
    # Modify board
    pcbnew.Refresh()

4. Check IsActionRunning

Prevent recursive plugin execution:
def Run(self):
    if pcbnew.IsActionRunning():
        return
    # Plugin logic

5. Use Relative Paths

For icons and resources, use paths relative to the plugin file:
import os

def defaults(self):
    plugin_dir = os.path.dirname(__file__)
    self.icon_file_name = os.path.join(plugin_dir, "icon.png")

C++ Plugin API

The underlying C++ API defines these base classes:

ACTION_PLUGIN

Abstract base class for action plugins. Pure virtual methods:
  • GetCategoryName() → wxString
  • GetName() → wxString
  • GetClassName() → wxString
  • GetDescription() → wxString
  • GetShowToolbarButton() → bool
  • GetIconFileName(bool aDark) → wxString
  • GetPluginPath() → wxString
  • GetObject() → void*
  • Run() → void
Methods:
  • register_action() - Register with plugin manager

ACTION_PLUGINS

Static manager for all action plugins. Static methods:
  • register_action(ACTION_PLUGIN*) - Register a plugin
  • deregister_object(void*) - Deregister by object pointer
  • GetAction(wxString) → ACTION_PLUGIN* - Get by name
  • GetAction(int) → ACTION_PLUGIN* - Get by index
  • GetActionsCount() → int - Get count
  • IsActionRunning() → bool - Check if any action is running
  • SetActionRunning(bool) - Set running state
  • UnloadAll() - Unload all plugins

See Also

Build docs developers (and LLMs) love