KiCad 10 Python Plugin Toolbar Button: Complete Setup Guide for Linux

The toolbar button is one field away

The button won’t appear unless you set self.show_toolbar_button = True inside your ActionPlugin subclass’s defaults() method. That field defaults to False. KiCad will happily load and register your plugin without surfacing any button — it just lives quietly under Tools → External Plugins. Everything else — folder location, packaging, icon — is secondary to getting that flag right.

Correct folder structure on Linux

For KiCad 10 on Fedora (or any Linux distro), manual plugin installs go here:

~/.local/share/kicad/10.0/scripting/plugins/

The 3rdparty/plugins path is owned by the Plugin and Content Manager. Dropping files there by hand causes the kind of grief you ran into. Stick to scripting/plugins for anything you’re installing manually or actively developing.

A bare .py file can work for the simplest cases. But if you want an icon — which you need for a toolbar button — your plugin should be a directory (a Python package).

Single-file layout

~/.local/share/kicad/10.0/scripting/plugins/
└── my_track_plugin.py
~/.local/share/kicad/10.0/scripting/plugins/
└── my_track_plugin/
    ├── __init__.py
    ├── plugin.py      (optional — separate file for your logic)
    └── icon.png       (24×24 pixels, RGBA)

The __init__.py file

This is KiCad’s entry point into your package. It runs when the PCB editor starts and scans the plugins folder. At minimum it needs to instantiate your ActionPlugin subclass and call .register() on it. That call is what tells KiCad the plugin exists.

import pcbnew
import os

class TrackModifierPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Modify Selected Tracks"
        self.category = "Modify PCB"
        self.description = "Adjusts width and clearance on selected tracks"
        self.show_toolbar_button = True
        self.icon_file_name = os.path.join(os.path.dirname(__file__), 'icon.png')

    def Run(self):
        board = pcbnew.GetBoard()
        # your track modification logic here
        pcbnew.Refresh()

TrackModifierPlugin().register()

The os.path.dirname(__file__) trick makes the icon path resolve correctly regardless of where KiCad is installed. If the PNG is missing KiCad falls back to a generic icon instead of erroring out.

Call pcbnew.Refresh() at the end of Run(). Without it your changes modify the board data model but the canvas won’t repaint until the user does something else.

show_toolbar_button: just the one field

Set self.show_toolbar_button = True and a button appears in the PCB editor’s top toolbar. Omit it — or leave it False — and the plugin is reachable only via the menu. No other configuration required for the button itself.

Users can override this per-session from the PCB Editor preferences panel, which has a section for external plugin toolbar buttons. So True in code is just the default; it’s not forced on anyone.

Refreshing without restarting KiCad

During development you don’t need to restart KiCad after every change. Go to Tools → External Plugins → Refresh Plugins. That re-scans the plugins folders and re-imports the packages. Fast enough to iterate on Run() without losing your board state.

Reusing your existing IPC script

Your command-line IPC script doesn’t need to be rewritten. Import its functions into Run() and call them. The plugin just provides the GUI trigger. The board manipulation code can stay exactly as is — the IPC interface works the same way whether called from the terminal or from inside the editor’s Python environment.

One difference worth knowing: when running inside the editor you can call pcbnew.GetBoard() directly to get the live board object, which is often simpler than going through IPC. Either approach works; mixing them is also fine.

Why PCM zips kept crashing

The PCM expects a rigid archive layout. A wrong structure doesn’t produce a helpful error — it just crashes on import. For a Python plugin the zip must look exactly like this:

my-plugin-1.0.0.zip
├── plugins/
│   ├── __init__.py
│   └── icon.png
├── resources/
│   └── icon.png        (64×64, shown in the PCM dialog)
└── metadata.json

The plugins/ directory at the zip root is mandatory. Your Python files go directly inside it — no nested subdirectory. The resources/ icon is separate from the toolbar icon and is only used by the PCM dialog UI.

The metadata.json file has a strict schema. Required fields include identifier (reverse-DNS format, e.g. com.yourname.myplugin), name, description, description_full, type (set to "plugin"), author, and a versions array. The KiCad addon developer docs cover the full spec.

For local development, skip PCM packaging entirely. Drop the package directory into scripting/plugins and hit Refresh. PCM packaging is only worth the overhead when you’re distributing to other people.

Sources


Related Articles

Similar Posts