
Qgis Syntax Plugins
- 8 installs
- 29 repo stars
- Updated July 8, 2026
- openaec-foundation/qgis-claude-skill-package
Helps with ai & agent building tasks.
About
qgis-syntax-plugins is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- qgis-syntax-plugins
- AI & Agent Building
- AI-coding skill
Qgis Syntax Plugins by the numbers
- 8 all-time installs (skills.sh)
- Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/qgis-claude-skill-package --skill qgis-syntax-pluginsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 29 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/qgis-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
qgis-syntax-plugins
Quick Reference
Plugin File Structure
| File | Required | Purpose |
|---|---|---|
__init__.py | YES | Contains classFactory() entry point |
metadata.txt | YES | Plugin metadata (name, version, description) |
mainPlugin.py | YES | Main plugin class with initGui() and unload() |
resources.qrc | NO | Qt resource definitions (icons, assets) |
resources.py | NO | Compiled resources via pyrcc5 |
form.ui | NO | Qt Designer form file |
icon.png | NO | Plugin icon (recommended) |
LICENSE | YES* | Required for QGIS Plugin Repository submission |
Plugin Lifecycle
QGIS Startup
|
v
classFactory(iface) ---------> returns plugin instance
|
v
initGui() -------------------> create menus, toolbars, actions, dock widgets
|
v
[plugin active -- user interacts]
|
v
unload() --------------------> remove ALL GUI elements, disconnect ALL signals
|
v
Plugin deactivatedPlugin Installation Paths
| Platform | Path |
|---|---|
| User plugins | ~/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins (Windows) |
| User plugins | ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins (Linux) |
| System plugins | <qgis_prefix>/python/plugins |
| Custom path | Set QGIS_PLUGINPATH environment variable |
Key iface Methods
| Method | Purpose |
|---|---|
iface.addToolBarIcon(action) | Add icon to plugin toolbar |
iface.removeToolBarIcon(action) | Remove icon from plugin toolbar |
iface.addPluginToMenu(menu_name, action) | Add to Plugins menu |
iface.removePluginMenu(menu_name, action) | Remove from Plugins menu |
iface.addPluginToVectorMenu(name, action) | Add to Vector menu |
iface.addPluginToRasterMenu(name, action) | Add to Raster menu |
iface.addPluginToDatabaseMenu(name, action) | Add to Database menu |
iface.addPluginToWebMenu(name, action) | Add to Web menu |
iface.addDockWidget(area, widget) | Add dock widget to main window |
iface.removeDockWidget(widget) | Remove dock widget |
iface.mainWindow() | Get main window (use as parent for dialogs) |
iface.mapCanvas() | Get the map canvas |
iface.activeLayer() | Get currently selected layer |
iface.messageBar() | Get the message bar for notifications |
---
Critical Warnings
ALWAYS implement unload() to remove ALL GUI elements, menu entries, toolbar icons, and dock widgets added in initGui(). Failure causes ghost UI elements that persist after plugin deactivation.
ALWAYS use self.iface.mainWindow() as the parent for QActions and dialogs. This ensures proper window management and garbage collection.
ALWAYS disconnect ALL signal connections in unload(). Dangling connections cause crashes when signals fire after plugin objects are destroyed.
NEVER access iface, QgsProject.instance(), or any GUI object from a background thread. This causes segfaults and silent crashes.
NEVER raise exceptions in QgsTask.run(). Return False to indicate failure instead.
NEVER leave compiled files (resources_rc.py, ui_*.py) in the repository. Generate them during build with pyrcc5 and pyuic5.
ALWAYS set objectName on QActions via action.setObjectName("uniqueName"). This prevents conflicts with other plugins and enables QGIS to save toolbar customizations.
ALWAYS track every GUI element you create in initGui() as an instance attribute so unload() can remove it.
---
Decision Tree: Plugin Type Selection
What does the plugin need to do?
|
+-- Add a toolbar button / menu item that runs a function?
| --> Minimal plugin (QAction + run method)
|
+-- Show a dialog with input fields?
| --> Dialog plugin (QAction + QDialog from .ui file)
|
+-- Show a persistent panel?
| --> Dock widget plugin (QDockWidget added via iface.addDockWidget)
|
+-- Add a Processing algorithm?
| --> Processing provider plugin (see qgis-syntax-processing-scripts)
|
+-- Interact with the map canvas?
| --> Map tool plugin (QgsMapTool subclass)---
Essential Patterns
Pattern 1: classFactory Entry Point
The __init__.py file MUST contain classFactory():
def classFactory(iface):
"""Load the plugin class. Called by QGIS on plugin startup."""
from .mainPlugin import MyPlugin
return MyPlugin(iface)QGIS calls this function with the QgisInterface object. It MUST return an instance of the plugin class.
Pattern 2: Plugin Class Skeleton
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtGui import QIcon
import os
class MyPlugin:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
def initGui(self):
icon_path = os.path.join(self.plugin_dir, "icon.png")
action = QAction(
QIcon(icon_path),
"My Plugin",
self.iface.mainWindow()
)
action.setObjectName("myPluginAction")
action.triggered.connect(self.run)
self.iface.addToolBarIcon(action)
self.iface.addPluginToMenu("&My Plugin", action)
self.actions.append(action)
def unload(self):
for action in self.actions:
self.iface.removePluginMenu("&My Plugin", action)
self.iface.removeToolBarIcon(action)
def run(self):
passPattern 3: metadata.txt Required Fields
[general]
name=My Plugin Name
qgisMinimumVersion=3.0
description=Short one-line description
about=Longer multi-line description
version=1.0.0
author=Author Name
email=author@example.com
repository=https://github.com/author/my-pluginRequired fields: name, qgisMinimumVersion, description, about, version, author, email, repository.
If qgisMaximumVersion is omitted, it defaults to major.99 (e.g., 3.99 for qgisMinimumVersion=3.0).
Pattern 4: Plugin Settings Storage
from qgis.core import QgsSettings
class MyPlugin:
def __init__(self, iface):
self.iface = iface
self.settings = QgsSettings()
def save_setting(self, key, value):
self.settings.setValue(f"MyPlugin/{key}", value)
def load_setting(self, key, default=None):
return self.settings.value(f"MyPlugin/{key}", default)ALWAYS prefix settings keys with the plugin name to avoid collisions.
Pattern 5: Qt Designer Dialog Integration
from qgis.PyQt import uic
import os
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.dirname(__file__), "dialog.ui")
)
class MyDialog(QDialog, FORM_CLASS):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)Pattern 6: Resource Compilation
Define resources in resources.qrc:
<RCC>
<qresource prefix="/plugins/myplugin">
<file>icon.png</file>
</qresource>
</RCC>Compile: pyrcc5 -o resources.py resources.qrc
Import in plugin: from . import resources
---
Common Operations
Add to Specific Menu Category
# Vector menu
self.iface.addPluginToVectorMenu("&My Plugin", self.action)
self.iface.removePluginVectorMenu("&My Plugin", self.action)
# Raster menu
self.iface.addPluginToRasterMenu("&My Plugin", self.action)
self.iface.removePluginRasterMenu("&My Plugin", self.action)
# Database menu
self.iface.addPluginToDatabaseMenu("&My Plugin", self.action)
self.iface.removePluginDatabaseMenu("&My Plugin", self.action)Add a Dock Widget
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QDockWidget, QWidget
def initGui(self):
self.dock = QDockWidget("My Panel", self.iface.mainWindow())
self.dock.setObjectName("myPluginDock")
self.dock.setWidget(QWidget())
self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock)
def unload(self):
self.iface.removeDockWidget(self.dock)
del self.dockShow Messages to Users
# Message bar (non-blocking)
from qgis.core import Qgis
self.iface.messageBar().pushMessage(
"My Plugin", "Operation complete", level=Qgis.Success, duration=3
)
# Log messages (for debugging)
from qgis.core import QgsMessageLog
QgsMessageLog.logMessage("Debug info", "My Plugin", Qgis.Info)Publishing Checklist
1. Plugin folder name: ASCII letters, digits, underscore, minus ONLY. NEVER start with a digit. 2. Package as ZIP: plugin.zip containing pluginfolder/ with all files. 3. Submit to https://plugins.qgis.org/ (requires OSGeo ID). 4. Staff approval required before publication. 5. Version MUST be unique across submissions.
---
Reference Links
- references/methods.md -- Plugin lifecycle methods, metadata.txt fields, QgisInterface methods
- references/examples.md -- Complete plugin skeletons, dialog plugins, dock widget plugins
- references/anti-patterns.md -- Plugin development pitfalls and fixes
Official Sources
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/plugins/index.html
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/plugins/plugins.html
- https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/plugins/releasing.html
- https://qgis.org/pyqgis/master/gui/QgisInterface.html
qgis-syntax-plugins — Anti-Patterns
AP-1: Incomplete unload() Method
Wrong
def initGui(self):
self.action = QAction("My Plugin", self.iface.mainWindow())
self.iface.addToolBarIcon(self.action)
self.iface.addPluginToMenu("&My Plugin", self.action)
self.dock = QDockWidget("Panel", self.iface.mainWindow())
self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock)
def unload(self):
self.iface.removePluginMenu("&My Plugin", self.action)
# MISSING: removeToolBarIcon and removeDockWidgetWhy It Fails
Ghost toolbar icons and dock widgets persist after plugin deactivation. Users see duplicate UI elements each time they re-enable the plugin.
Correct
def unload(self):
self.iface.removePluginMenu("&My Plugin", self.action)
self.iface.removeToolBarIcon(self.action)
self.iface.removeDockWidget(self.dock)
del self.dockRule: ALWAYS remove every GUI element that initGui() creates. Keep a list of all actions and widgets.
---
AP-2: Creating GUI Elements in __init__
Wrong
def __init__(self, iface):
self.iface = iface
self.action = QAction("My Plugin", self.iface.mainWindow()) # WRONG
self.iface.addToolBarIcon(self.action) # WRONGWhy It Fails
__init__ is called during QGIS startup for all installed plugins, even disabled ones. Creating GUI elements here adds UI for plugins the user has not enabled.
Correct
def __init__(self, iface):
self.iface = iface
# Store reference only — no GUI creation
def initGui(self):
self.action = QAction("My Plugin", self.iface.mainWindow())
self.iface.addToolBarIcon(self.action)Rule: NEVER create GUI elements in __init__. ALWAYS use initGui().
---
AP-3: Accessing GUI from Background Thread
Wrong
from qgis.core import QgsTask, QgsApplication
class MyTask(QgsTask):
def __init__(self, iface):
super().__init__("My Task")
self.iface = iface
def run(self):
# WRONG — accessing iface from background thread
layer = self.iface.activeLayer()
self.iface.messageBar().pushMessage("Done", "Result", level=Qgis.Info)
return TrueWhy It Fails
Qt GUI objects are NOT thread-safe. Accessing iface, QgsProject.instance(), or any widget from a background thread causes segfaults, deadlocks, or silent data corruption.
Correct
class MyTask(QgsTask):
def __init__(self, data):
super().__init__("My Task")
self.data = data # Pass copies, not live references
self.result_data = None
def run(self):
# Process data only — no GUI access
self.result_data = self.process(self.data)
return True
def finished(self, result):
# finished() runs on the main thread — GUI access is safe here
if result:
iface.messageBar().pushMessage(
"Done", "Processing complete", level=Qgis.Success
)Rule: NEVER access iface or any GUI object in QgsTask.run(). Use finished() for GUI updates.
---
AP-4: Missing objectName on QActions
Wrong
def initGui(self):
self.action = QAction("My Plugin", self.iface.mainWindow())
# No objectName setWhy It Fails
QGIS uses objectName to persist toolbar customizations. Without it, toolbar positions reset on restart. Multiple plugins with unnamed actions cause conflicts.
Correct
def initGui(self):
self.action = QAction("My Plugin", self.iface.mainWindow())
self.action.setObjectName("myPluginMainAction")Rule: ALWAYS call setObjectName() with a unique string on every QAction.
---
AP-5: Raising Exceptions in QgsTask.run()
Wrong
class MyTask(QgsTask):
def run(self):
data = self.load_data()
if data is None:
raise ValueError("No data found") # WRONG
return TrueWhy It Fails
Unhandled exceptions in QgsTask.run() crash QGIS or cause the task to hang indefinitely without calling finished().
Correct
class MyTask(QgsTask):
def run(self):
try:
data = self.load_data()
if data is None:
self.error_msg = "No data found"
return False
return True
except Exception as e:
self.error_msg = str(e)
return False
def finished(self, result):
if not result:
QgsMessageLog.logMessage(
self.error_msg, "My Plugin", Qgis.Critical
)Rule: NEVER raise exceptions in QgsTask.run(). Return False and log the error.
---
AP-6: Committing Compiled Files to Version Control
Wrong
my_plugin/
├── resources.py # COMPILED — should not be in repo
├── resources.qrc # Source — OK
├── ui_dialog.py # COMPILED — should not be in repo
├── dialog.ui # Source — OKWhy It Fails
Compiled files are platform-specific and QGIS-version-specific. They cause merge conflicts and mask the actual source files. Different PyQt5 versions generate different output.
Correct
Add to .gitignore:
resources.py
resources_rc.py
ui_*.pyBuild during deployment:
pyrcc5 -o resources.py resources.qrc
pyuic5 -o ui_dialog.py dialog.uiRule: NEVER commit compiled resource or UI files. ALWAYS regenerate during build.
---
AP-7: Forgetting to Disconnect Signals
Wrong
def initGui(self):
self.iface.currentLayerChanged.connect(self.on_layer_changed)
def unload(self):
# MISSING: disconnect signal
passWhy It Fails
After plugin deactivation, the signal still fires and calls self.on_layer_changed on a partially destroyed object. This causes RuntimeError: wrapped C/C++ object has been deleted.
Correct
def initGui(self):
self.iface.currentLayerChanged.connect(self.on_layer_changed)
def unload(self):
self.iface.currentLayerChanged.disconnect(self.on_layer_changed)Rule: ALWAYS disconnect every signal connection in unload().
---
AP-8: Using Wrong Parent for Dialogs
Wrong
def run(self):
dlg = QDialog() # No parent — floats behind main window
dlg.exec_()Why It Fails
Dialogs without a parent window float independently, can get lost behind the main window, and are not properly garbage-collected by Qt.
Correct
def run(self):
dlg = QDialog(self.iface.mainWindow())
dlg.exec_()Rule: ALWAYS pass self.iface.mainWindow() as parent for dialogs and QActions.
---
AP-9: Plugin Folder Name with Invalid Characters
Wrong
My Plugin v2.0/
├── __init__.py
├── metadata.txtWhy It Fails
QGIS Plugin Repository rejects folder names with spaces, special characters, or names starting with digits. Python cannot import modules with spaces in the name.
Correct
my_plugin_v2/
├── __init__.py
├── metadata.txtRule: Plugin folder names MUST contain only ASCII letters (A-Z, a-z), digits (0-9), underscores, and hyphens. NEVER start with a digit.
---
AP-10: Unprefixed Settings Keys
Wrong
self.settings.setValue("lastDirectory", "/home/user/data")Why It Fails
Settings are global. Without a plugin-specific prefix, settings collide with other plugins or QGIS core settings.
Correct
self.settings.setValue("MyPlugin/lastDirectory", "/home/user/data")Rule: ALWAYS prefix QgsSettings keys with the plugin name.
qgis-syntax-plugins — Examples
Example 1: Minimal Plugin Skeleton
Complete, working minimal plugin with toolbar button and menu entry.
File: __init__.py
def classFactory(iface):
"""Load the plugin class."""
from .minimal_plugin import MinimalPlugin
return MinimalPlugin(iface)File: metadata.txt
[general]
name=Minimal Plugin
qgisMinimumVersion=3.0
description=A minimal QGIS plugin skeleton
about=Demonstrates the minimum required plugin structure with toolbar icon and menu entry.
version=0.1.0
author=Developer Name
email=dev@example.com
repository=https://github.com/developer/minimal-plugin
icon=icon.png
tags=example,skeleton,minimal
category=VectorFile: minimal_plugin.py
from qgis.PyQt.QtWidgets import QAction, QMessageBox
from qgis.PyQt.QtGui import QIcon
from qgis.core import Qgis
import os
class MinimalPlugin:
"""Minimal QGIS plugin demonstrating required lifecycle methods."""
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
def initGui(self):
"""Create the menu entries and toolbar icons."""
icon_path = os.path.join(self.plugin_dir, "icon.png")
action = QAction(
QIcon(icon_path),
"Minimal Plugin",
self.iface.mainWindow()
)
action.setObjectName("minimalPluginAction")
action.triggered.connect(self.run)
self.iface.addToolBarIcon(action)
self.iface.addPluginToMenu("&Minimal Plugin", action)
self.actions.append(action)
def unload(self):
"""Remove the plugin menu item and icon."""
for action in self.actions:
self.iface.removePluginMenu("&Minimal Plugin", action)
self.iface.removeToolBarIcon(action)
def run(self):
"""Run the plugin logic."""
layer = self.iface.activeLayer()
if layer is None:
self.iface.messageBar().pushMessage(
"Minimal Plugin",
"No active layer selected",
level=Qgis.Warning,
duration=3
)
return
self.iface.messageBar().pushMessage(
"Minimal Plugin",
f"Active layer: {layer.name()} ({layer.featureCount()} features)",
level=Qgis.Info,
duration=5
)---
Example 2: Dialog Plugin with Qt Designer
Plugin that shows a dialog built with Qt Designer.
File: dialog.ui (Qt Designer)
Create this file in Qt Designer. It defines a dialog with an input field and OK/Cancel buttons.
File: dialog.py
from qgis.PyQt.QtWidgets import QDialog
from qgis.PyQt import uic
import os
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.dirname(__file__), "dialog.ui")
)
class BufferDialog(QDialog, FORM_CLASS):
"""Dialog for buffer distance input."""
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)File: buffer_plugin.py
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtGui import QIcon
from qgis.core import (
Qgis, QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry
)
import os
import processing
from .dialog import BufferDialog
class BufferPlugin:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
self.dlg = None
def initGui(self):
icon_path = os.path.join(self.plugin_dir, "icon.png")
action = QAction(
QIcon(icon_path),
"Buffer Tool",
self.iface.mainWindow()
)
action.setObjectName("bufferPluginAction")
action.triggered.connect(self.run)
self.iface.addToolBarIcon(action)
self.iface.addPluginToVectorMenu("&Buffer Tool", action)
self.actions.append(action)
def unload(self):
for action in self.actions:
self.iface.removePluginVectorMenu("&Buffer Tool", action)
self.iface.removeToolBarIcon(action)
def run(self):
if self.dlg is None:
self.dlg = BufferDialog(self.iface.mainWindow())
result = self.dlg.exec_()
if result:
distance = self.dlg.distanceSpinBox.value()
layer = self.iface.activeLayer()
if layer is None:
return
params = {
"INPUT": layer,
"DISTANCE": distance,
"SEGMENTS": 5,
"OUTPUT": "memory:"
}
result = processing.run("native:buffer", params)
QgsProject.instance().addMapLayer(result["OUTPUT"])---
Example 3: Dock Widget Plugin
Plugin with a persistent side panel.
from qgis.PyQt.QtWidgets import (
QAction, QDockWidget, QVBoxLayout, QWidget,
QLabel, QPushButton, QListWidget
)
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QIcon
from qgis.core import Qgis, QgsProject
import os
class LayerInfoPlugin:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
self.dock = None
def initGui(self):
icon_path = os.path.join(self.plugin_dir, "icon.png")
action = QAction(
QIcon(icon_path),
"Layer Info Panel",
self.iface.mainWindow()
)
action.setObjectName("layerInfoAction")
action.setCheckable(True)
action.triggered.connect(self.toggle_dock)
self.iface.addToolBarIcon(action)
self.iface.addPluginToMenu("&Layer Info", action)
self.actions.append(action)
# Create dock widget
self.dock = QDockWidget("Layer Info", self.iface.mainWindow())
self.dock.setObjectName("layerInfoDock")
# Build dock content
container = QWidget()
layout = QVBoxLayout(container)
self.info_label = QLabel("Select a layer")
self.refresh_btn = QPushButton("Refresh")
self.refresh_btn.clicked.connect(self.refresh_info)
self.field_list = QListWidget()
layout.addWidget(self.info_label)
layout.addWidget(self.field_list)
layout.addWidget(self.refresh_btn)
self.dock.setWidget(container)
self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock)
# Connect to layer change signal
self.iface.currentLayerChanged.connect(self.on_layer_changed)
def unload(self):
# Disconnect signals
self.iface.currentLayerChanged.disconnect(self.on_layer_changed)
# Remove dock widget
self.iface.removeDockWidget(self.dock)
del self.dock
self.dock = None
# Remove menu and toolbar entries
for action in self.actions:
self.iface.removePluginMenu("&Layer Info", action)
self.iface.removeToolBarIcon(action)
def toggle_dock(self, checked):
if self.dock is not None:
self.dock.setVisible(checked)
def on_layer_changed(self, layer):
self.refresh_info()
def refresh_info(self):
layer = self.iface.activeLayer()
self.field_list.clear()
if layer is None:
self.info_label.setText("No layer selected")
return
self.info_label.setText(
f"{layer.name()} - {layer.featureCount()} features"
)
if hasattr(layer, "fields"):
for field in layer.fields():
self.field_list.addItem(
f"{field.name()} ({field.typeName()})"
)---
Example 4: Plugin with Settings
from qgis.core import QgsSettings
class SettingsPlugin:
SETTING_PREFIX = "SettingsPlugin"
def __init__(self, iface):
self.iface = iface
self.settings = QgsSettings()
def save_last_directory(self, path):
self.settings.setValue(
f"{self.SETTING_PREFIX}/lastDirectory", path
)
def load_last_directory(self):
return self.settings.value(
f"{self.SETTING_PREFIX}/lastDirectory", ""
)
def save_buffer_distance(self, distance):
self.settings.setValue(
f"{self.SETTING_PREFIX}/bufferDistance", distance
)
def load_buffer_distance(self):
return float(self.settings.value(
f"{self.SETTING_PREFIX}/bufferDistance", 100.0
))---
Example 5: Plugin with Custom Toolbar
from qgis.PyQt.QtWidgets import QAction, QToolBar
from qgis.PyQt.QtGui import QIcon
import os
class MultiToolPlugin:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.actions = []
self.toolbar = None
def initGui(self):
# Create a dedicated toolbar
self.toolbar = self.iface.addToolBar("My Tools")
self.toolbar.setObjectName("myToolsToolbar")
# Add multiple actions
for name, callback in [
("Tool A", self.run_a),
("Tool B", self.run_b),
("Tool C", self.run_c),
]:
action = QAction(name, self.iface.mainWindow())
action.setObjectName(f"myTools_{name.replace(' ', '')}")
action.triggered.connect(callback)
self.toolbar.addAction(action)
self.iface.addPluginToMenu("&My Tools", action)
self.actions.append(action)
def unload(self):
for action in self.actions:
self.iface.removePluginMenu("&My Tools", action)
# Remove the custom toolbar
if self.toolbar is not None:
del self.toolbar
self.toolbar = None
def run_a(self):
pass
def run_b(self):
pass
def run_c(self):
pass---
Example 6: Plugin Builder Workflow
Plugin Builder is a QGIS plugin that generates a complete plugin skeleton.
Steps
1. Install Plugin Builder from QGIS Plugin Manager (Plugins > Manage and Install Plugins) 2. Run Plugin Builder (Plugins > Plugin Builder > Plugin Builder) 3. Fill in plugin details (name, module name, description, author) 4. Choose plugin type (Tool button, Dialog, Dock widget, Processing provider) 5. Plugin Builder generates all required files
Generated Files
my_generated_plugin/
├── __init__.py # classFactory entry point
├── metadata.txt # Plugin metadata
├── my_generated_plugin.py # Main plugin class
├── my_generated_plugin_dialog.py # Dialog class
├── my_generated_plugin_dialog_base.ui # Qt Designer form
├── resources.qrc # Resource definitions
├── icon.png # Default icon
├── Makefile # Build automation
├── pb_tool.cfg # Plugin Builder config
├── pylintrc # Linting config
├── README.txt # Documentation
└── test/ # Test directory
└── __init__.pyBuild Commands (from Makefile)
# Compile resources
pyrcc5 -o resources.py resources.qrc
# Compile UI files
pyuic5 -o my_generated_plugin_dialog_base.py my_generated_plugin_dialog_base.ui
# Deploy to QGIS plugin directory
# Copy plugin folder to ~/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgis-syntax-plugins — Methods Reference
Plugin Lifecycle Methods
classFactory(iface)
| Aspect | Detail |
|---|---|
| Location | __init__.py |
| Parameter | iface — QgisInterface instance |
| Returns | Plugin class instance |
| Called by | QGIS plugin loader on startup |
def classFactory(iface):
from .mainPlugin import MyPlugin
return MyPlugin(iface)__init__(self, iface)
| Aspect | Detail |
|---|---|
| Purpose | Store iface reference, initialize variables |
| Rules | NEVER create GUI elements here — use initGui() instead |
| Rules | ALWAYS store self.iface = iface |
initGui(self)
| Aspect | Detail |
|---|---|
| Purpose | Create and register ALL GUI elements |
| Called when | Plugin is activated by user or at QGIS startup if enabled |
| Rules | ALWAYS track created elements as instance attributes |
| Rules | ALWAYS set objectName on QActions |
| Rules | ALWAYS use self.iface.mainWindow() as parent |
unload(self)
| Aspect | Detail |
|---|---|
| Purpose | Remove ALL GUI elements and disconnect ALL signals |
| Called when | Plugin is deactivated or QGIS shuts down |
| Rules | MUST remove every menu item added in initGui() |
| Rules | MUST remove every toolbar icon added in initGui() |
| Rules | MUST remove every dock widget added in initGui() |
| Rules | MUST disconnect every signal connected in initGui() |
---
metadata.txt Fields
Required Fields
| Field | Description | Example |
|---|---|---|
name | Display name of the plugin | My Awesome Plugin |
qgisMinimumVersion | Minimum QGIS version | 3.0 |
description | One-line summary | Performs spatial analysis on vector layers |
about | Multi-line detailed description | This plugin provides... |
version | Semantic version | 1.0.0 |
author | Author name | John Doe |
email | Author email | john@example.com |
repository | Source code URL | https://github.com/author/plugin |
Optional Fields
| Field | Description | Default |
|---|---|---|
qgisMaximumVersion | Maximum QGIS version | major.99 |
category | Plugin category | None |
icon | Icon filename (relative to plugin dir) | None |
experimental | Mark as experimental | False |
deprecated | Mark as deprecated | False |
tags | Comma-separated search tags | None |
homepage | Plugin homepage URL | None |
tracker | Issue tracker URL | None |
changelog | Version changelog text | None |
hasProcessingProvider | Plugin provides Processing algorithms | no |
server | Plugin is for QGIS Server | False |
plugin_dependencies | Comma-separated plugin names | None |
Category Values
| Value | Use for |
|---|---|
Raster | Raster data analysis plugins |
Vector | Vector data analysis plugins |
Database | Database interaction plugins |
Mesh | Mesh data plugins |
Web | Web service plugins |
---
QgisInterface (iface) Methods
Menu Integration
| Method | Purpose |
|---|---|
addPluginToMenu(name, action) | Add action to Plugins > name submenu |
removePluginMenu(name, action) | Remove action from Plugins > name submenu |
addPluginToVectorMenu(name, action) | Add to Vector menu |
removePluginVectorMenu(name, action) | Remove from Vector menu |
addPluginToRasterMenu(name, action) | Add to Raster menu |
removePluginRasterMenu(name, action) | Remove from Raster menu |
addPluginToDatabaseMenu(name, action) | Add to Database menu |
removePluginDatabaseMenu(name, action) | Remove from Database menu |
addPluginToWebMenu(name, action) | Add to Web menu |
removePluginWebMenu(name, action) | Remove from Web menu |
Toolbar Integration
| Method | Purpose |
|---|---|
addToolBarIcon(action) | Add icon to the Plugins toolbar |
removeToolBarIcon(action) | Remove icon from the Plugins toolbar |
addToolBar(name) | Create a new named toolbar |
addToolBarWidget(widget) | Add custom widget to Plugins toolbar |
Dock Widget Integration
| Method | Purpose |
|---|---|
addDockWidget(area, widget) | Add a dock widget to the main window |
removeDockWidget(widget) | Remove a dock widget |
UI Access
| Method | Returns | Purpose |
|---|---|---|
mainWindow() | QMainWindow | Main QGIS window (use as parent for dialogs) |
mapCanvas() | QgsMapCanvas | The map canvas widget |
layerTreeView() | QgsLayerTreeView | Layer panel tree view |
activeLayer() | QgsMapLayer or None | Currently selected layer |
messageBar() | QgsMessageBar | Message bar for notifications |
statusBarIface() | QgsStatusBar | Access to status bar |
Layer Operations
| Method | Purpose |
|---|---|
addVectorLayer(path, name, provider) | Add vector layer to project |
addRasterLayer(path, name, provider) | Add raster layer to project |
setActiveLayer(layer) | Set the active layer |
zoomToActiveLayer() | Zoom to active layer extent |
---
QgsSettings Methods
| Method | Purpose |
|---|---|
setValue(key, value) | Store a setting |
value(key, defaultValue=None) | Retrieve a setting |
remove(key) | Remove a setting |
contains(key) | Check if setting exists |
allKeys() | Get all setting keys |
childGroups() | Get child group names |
beginGroup(prefix) | Set group prefix |
endGroup() | End group prefix |
ALWAYS prefix plugin settings with the plugin name: "MyPlugin/settingName".
---
Resource Compilation Commands
| Command | Purpose |
|---|---|
pyrcc5 -o resources.py resources.qrc | Compile Qt resources to Python |
pyuic5 -o ui_dialog.py dialog.ui | Compile Qt Designer .ui to Python |
NEVER commit compiled files to version control. ALWAYS regenerate during build.